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 +
10
13
14#include <cfloat>
15#include <climits>
16#include <cmath>
17
18#include <algorithm>
19#include <sstream>
20
21#include <QLineEdit>
22
23using namespace Mantid::Kernel;
24using namespace Mantid::API;
26
27namespace // anonymous
28{
40double stringToRoundedNumber(const std::string &s) {
41 // Using std::istringstream is a nice way to do string-to-double conversion
42 // in this situation as it rounds numbers for us at the same time.
43 // Unfortunately,
44 // commas seem to confuse it ("0,0,0" is converted to "0" without any
45 // warning).
46 const bool containsComma = s.find(",") != std::string::npos;
47 if (containsComma)
48 throw std::runtime_error("string contains a comma");
49
50 return std::stod(s);
51}
52
61bool isValidPropertyValue(Mantid::Kernel::Property const *prop, const std::string &value) {
62 const auto guineaPig = std::shared_ptr<Property>(prop->clone());
63 return guineaPig->setValue(value).empty();
64}
65
76bool isEmptyNumMacro(const std::string &value, const double value_d) {
77 using namespace Mantid;
78
79 // Catch instances of Python's "sys.maxint" which otherwise seem to fall
80 // through our net.
81 if (value == "2.14748e+09")
82 return true;
83
84 static const std::vector<double> EMPTY_NUM_MACROS = {EMPTY_DBL(),
85 -DBL_MAX,
86 DBL_MAX,
87 static_cast<double>(EMPTY_INT()),
88 static_cast<double>(EMPTY_LONG()),
89 static_cast<double>(-INT_MAX),
90 static_cast<double>(-LONG_MAX)};
91
92 return std::find(EMPTY_NUM_MACROS.begin(), EMPTY_NUM_MACROS.end(), value_d) != EMPTY_NUM_MACROS.end();
93}
94
102bool isOptionalProperty(Mantid::Kernel::Property const *prop) {
103 return isValidPropertyValue(prop, "") || isValidPropertyValue(prop, prop->getDefault());
104}
105
116std::string createFieldPlaceholderText(Mantid::Kernel::Property const *prop) {
117 const std::string defaultValue = prop->getDefault();
118 if (defaultValue.empty())
119 return "";
120
121 if (!isValidPropertyValue(prop, defaultValue))
122 return "";
123
124 // It seems likely that any instance of "-0" or "-0.0" should be replaced with
125 // an appropriate
126 // EMPTY_* macro, but for now just display them as they appear in the Wiki.
127 if (defaultValue == "-0" || defaultValue == "-0.0")
128 return "0";
129
130 // If it can't be converted to a double, there is no reason to give
131 // it special handling
132 double roundedNumber;
133 try {
134 roundedNumber = stringToRoundedNumber(defaultValue);
135 } catch (std::exception &) {
136 return defaultValue;
137 }
138
139 if (isEmptyNumMacro(prop->getDefault(), roundedNumber))
140 return "";
141
142 // We'd like to round off any instances of "2.7999999999999998",
143 // "0.050000000000000003", or similar, but we want to keep the
144 // decimal point in values like "0.0" or "1.0" since they can be a
145 // visual clue that a double is expected.
146 static const std::size_t STRING_ROUNDING_LENGTH = 15;
147 if (defaultValue.length() >= STRING_ROUNDING_LENGTH) {
148 std::stringstream roundedValue;
149 roundedValue << roundedNumber;
150 return roundedValue.str();
151 }
152
153 return defaultValue;
154}
155} // anonymous namespace
156
157namespace MantidQt::API {
163ClickableLabel::ClickableLabel(QWidget *parent) : QLabel(parent) {}
168
174void ClickableLabel::mousePressEvent(QMouseEvent *event) {
175 UNUSED_ARG(event);
176 emit clicked();
177}
178
181PropertyWidget::PropertyWidget(Mantid::Kernel::Property *prop, QWidget *parent, QGridLayout *layout, int row)
182 : QWidget(parent), m_prop(prop), m_gridLayout(layout), m_parent(nullptr), m_row(row), // m_info(NULL),
183 m_doc(), m_replaceWSButton(nullptr), m_widgets(), m_lastValue(), m_error(), m_isOutputWsProp(false),
184 m_previousValue(), m_previous_isDynamicDefault(false), m_enteredValue(), m_entered_isDynamicDefault(false),
185 m_icons(), m_useHistory(true) {
186 if (!prop)
187 throw std::runtime_error("NULL Property passed to the PropertyWidget constructor.");
188 setObjectName(QString::fromStdString(prop->name()));
189
190 if (!m_gridLayout) {
191 // Create a LOCAL grid layout
192 m_gridLayout = new QGridLayout(this);
193
194 m_gridLayout->setSpacing(5);
195 this->setLayout(m_gridLayout);
196 m_row = 0;
197 m_parent = this;
198 } else {
199 // Use the parent of the provided QGridLayout when adding widgets
200 m_parent = parent;
201 // HACK - In this mode a property widget is not a true self-contained
202 // widget: it has no children
203 // of its own. By default, when added to a parent widget, it will be
204 // drawn invisble
205 // at the top left of the parent widget and obscure mouse clicks etc.
206 // The hack fix is to lower it down the visible stack.
207 this->lower();
208 }
209
210 auto *infoWidget = new QWidget();
211 infoWidget->setLayout(new QHBoxLayout(this));
212 infoWidget->layout()->setSpacing(1);
213 infoWidget->layout()->setContentsMargins(0, 0, 0, 0);
214 // `addLayout` sets parent of `infoWidget` correctly
215 m_gridLayout->addWidget(infoWidget, m_row, 4);
216
217 QMap<Info, std::pair<QString, QString>> pathsAndToolTips;
218 pathsAndToolTips[RESTORE] =
219 std::pair<QString, QString>(":/history.png", "This property had a previously-entered value. Click "
220 "to toggle it off and on.");
221 pathsAndToolTips[REPLACE] = std::pair<QString, QString>(
222 ":/replace.png", "A workspace with this name already exists and so will be overwritten.");
223 pathsAndToolTips[INVALID] = std::pair<QString, QString>(":/invalid.png", "");
224
225 std::vector<Info> labelOrder = {RESTORE, REPLACE, INVALID};
226
227 for (const Info &info : labelOrder) {
228 const QString iconPath = pathsAndToolTips[info].first;
229 const QString toolTip = pathsAndToolTips[info].second;
230
231 auto icon = new ClickableLabel(this);
232 icon->setPixmap(QPixmap(iconPath).scaledToHeight(15));
233 icon->setVisible(false);
234 icon->setToolTip(toolTip);
235
236 infoWidget->layout()->addWidget(icon);
237 m_icons[info] = icon;
238 }
239
240 // Qt will have parented the `infoWidget` to `m_gridLayout`, and re-parented all of the icons to `infoWidget`.
241 // However, some end-user classes need to be able to completely recreate the `PropertyWidget`,
242 // so for this reason we explicitly delete these objects when this widget is destroyed.
243 connect(this, &QObject::destroyed, this, [infoWidget]() {
244 infoWidget->deleteLater(); // Safe deferred deletion
245 });
246
247 connect(m_icons[RESTORE], SIGNAL(clicked()), this, SLOT(toggleUseHistory()));
248
250 m_doc = QString::fromStdString(prop->documentation());
251
252 if (!isOptionalProperty(prop)) {
253 if (!m_doc.isEmpty())
254 m_doc += ".\n\n";
255 m_doc += "This property is required.";
256 }
257
258 if (prop->direction() == Direction::Output && dynamic_cast<IWorkspaceProperty *>(prop))
259 m_isOutputWsProp = true;
260}
261
265
271void PropertyWidget::setValue(const QString &value) {
272 // Set the value in the widget itself.
275
276 // Set the current value in the property, and check its validation.
277 // TODO: this method could have a much better name!
279
280 // Emit the `valueChanged` signal only when both the widget (and its property)
281 // have their _current_ values, _AND_ those values are valid!
282 if (m_error.isEmpty()) {
284 }
285}
286
292void PropertyWidget::setPreviousValue(const QString &previousValue) {
293 m_useHistory = true;
294 m_previousValue = previousValue;
296
297 // Handle input workspace options that have been set to a workspace
298 // previously, but where the workspace no longer exists.
299 if (getValue() != previousValue) {
300 m_previousValue = "";
302 }
303
304 // Once we've made the history icon visible, it will stay that way for
305 // the lifetime of the property widget.
306 if (!m_previousValue.isEmpty() && m_previousValue.toStdString() != m_prop->getDefault()) {
308 m_icons[RESTORE]->setVisible(true);
309 }
310}
311
321
335 QString value = QString::fromStdString(m_prop->value());
336 bool isDynamicDefault = m_prop->isDynamicDefault();
337
338 m_icons[RESTORE]->setVisible(other->m_icons[RESTORE]->isVisible());
339 if (!upstream) {
340 // This clause transfers the state details, while making the new
341 // property's state consistent with its new value.
342 m_previousValue = other->m_previousValue;
344 m_previous_isDynamicDefault = other->m_previous_isDynamicDefault;
345 m_enteredValue = (m_useHistory ? other->m_enteredValue : value);
346 m_entered_isDynamicDefault = (m_useHistory ? other->m_entered_isDynamicDefault : isDynamicDefault);
347 } else {
348 // This clause treats the dynamic-default case:
349 // the use-history state for a dynamic-default value tracks the upstream state.
350 m_useHistory = upstream->m_useHistory;
351 m_previousValue = (m_useHistory ? value : other->m_previousValue);
352 m_previous_isDynamicDefault = (m_useHistory ? isDynamicDefault : other->m_previous_isDynamicDefault);
353 m_enteredValue = (m_useHistory ? other->m_enteredValue : value);
354 m_entered_isDynamicDefault = (m_useHistory ? other->m_entered_isDynamicDefault : isDynamicDefault);
355 }
356
357 setUseHistoryIcon(m_useHistory, isDynamicDefault);
358}
359
366 QString userError(error);
367 if (userError.isEmpty()) {
368 // Show "*" icon if the value is invalid for this property.
369 QString value = this->getValue().trimmed();
370 // Use the default if empty
371 if (value.isEmpty())
372 value = QString::fromStdString(m_prop->getDefault());
373
374 try {
375 userError = QString::fromStdString(m_prop->setValue(value.toStdString()));
376 } catch (std::exception &err_details) {
377 userError = QString::fromLatin1(err_details.what());
378 }
379 }
380 this->setError(userError.trimmed());
381
382 m_icons[INVALID]->setVisible(!m_error.isEmpty());
383 m_icons[INVALID]->setToolTip(m_error);
384 // Show "!" icon if a workspace would be overwritten.
385 if (m_isOutputWsProp) {
386 const bool wsExists = Mantid::API::AnalysisDataService::Instance().doesExist(getValue().toStdString());
387 m_icons[REPLACE]->setVisible(wsExists);
388 }
389}
390
392void PropertyWidget::replaceWSButtonClicked() { emit replaceWorkspaceName(QString::fromStdString(m_prop->name())); }
393
398 // This will be caught by the GenericDialog.
399 emit valueChanged(QString::fromStdString(m_prop->name()));
400}
401
407 QString value = this->getValue();
408 if (value != m_lastValue) {
409 // DO NOTHING if the value has not changed =>
410 // ignore focus changes that don't correspond to value changes.
412
413 if (value != m_previousValue) {
414 m_useHistory = false;
416
417 // The user has edited the property => disable dynamic-default values.
420 } else {
421 m_useHistory = true;
423 }
424
426
427 // The property's value is validated and updated here:
429
430 // As in `setValue`:
431 // emit the `valueChanged` signal only when both the widget (and its property)
432 // have their _current_ values, _AND_ those values are valid!
433 if (m_error.isEmpty())
435 }
436}
437
452
462void PropertyWidget::setUseHistoryIcon(bool useHistory, bool isDynamicDefault) {
463 const QString iconPath = useHistory ? (isDynamicDefault ? ":/history_dynamic_default.png" : ":/history.png")
464 : (isDynamicDefault ? ":/history_off_dynamic_default.png" : ":/history_off.png");
465 m_icons[RESTORE]->setPixmap(QPixmap(iconPath).scaledToHeight(15));
466
467 // When any user-entered value exists:
468 // OR when a value is a dynamic-default value:
469 // make the use-history icons visible.
470 if (!m_enteredValue.isEmpty() || isDynamicDefault)
471 m_icons[RESTORE]->setVisible(true);
472}
473
480 // Don't re-create it if it already exists
482 return;
483
484 auto const *wsProp = dynamic_cast<IWorkspaceProperty *>(m_prop);
485 if (wsProp && (m_prop->direction() == Direction::Output)) {
486 m_replaceWSButton = new QPushButton(QIcon(":/data_replace.png"), "", m_parent);
487 // MG: There is no way with the QIcon class to actually ask what size it is
488 // so I had to hard
489 // code this number here to get it to a sensible size
490 m_replaceWSButton->setMaximumWidth(32);
491 // m_wsbtn_tracker[btn ] = 1;
492 m_replaceWSButton->setToolTip("Replace input workspace");
493 connect(m_replaceWSButton, SIGNAL(clicked()), this, SLOT(replaceWSButtonClicked()));
494 connect(m_replaceWSButton, SIGNAL(clicked()), this, SLOT(valueChangedSlot()));
495 m_widgets.push_back(m_replaceWSButton);
496 // Place in the grid on column 2.
497 m_gridLayout->addWidget(m_replaceWSButton, m_row, 2);
498 m_replaceWSButton->setVisible(true);
499 }
500}
501
506void PropertyWidget::setError(const QString &error) { m_error = error.trimmed(); }
507
511 for (auto &widget : m_widgets)
512 widget->setEnabled(val);
513 QWidget::setEnabled(val);
514}
515
519 for (auto &widget : m_widgets)
520 widget->setVisible(val);
521 QWidget::setVisible(val);
522}
523
533 if (!isOptionalProperty(prop)) {
534 auto font = label->font();
535 font.setBold(true);
536 label->setFont(font);
537 }
538}
539
549 field->setPlaceholderText(QString::fromStdString(createFieldPlaceholderText(prop)));
550}
551
552} // namespace MantidQt::API
double value
The value of the point.
Definition FitMW.cpp:51
double error
#define UNUSED_ARG(x)
Function arguments are sometimes unused in certain implmentations but are required for documentation ...
Definition System.h:44
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.
Base class for widgets that will set Mantid::Kernel::Property* types.
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.
QString m_lastValue
Last modified value.
virtual QString getValue() const =0
Return the value of the property given the GUI state.
PropertyWidget(Mantid::Kernel::Property *prop, QWidget *parent=nullptr, QGridLayout *layout=nullptr, int row=-1)
Constructor.
QString m_enteredValue
Stores the last value 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)
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.
QList< QWidget * > m_widgets
All contained widgets.
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 setPrevious_isDynamicDefault(bool flag)
Set the isDynamicDefault flag associated with the previously-entered value.
void setUseHistoryIcon(bool useHistory, bool isDynamicDefault)
Sets the history on/off icons and the dynamic-default marker.
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.
bool m_previous_isDynamicDefault
Stores the isDynamicDefault flag corresponding to the previously entered value.
void transferHistoryState(const PropertyWidget *other, const PropertyWidget *upstream=nullptr)
Transfer the history state from another PropertyWidget, possibly additionally depending on the histor...
void setPreviousValue(const QString &previousValue)
Set this widget's previously-entered value.
bool m_entered_isDynamicDefault
Stores the isDynamicDefault flag corresponding to the last value entered by the user.
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:95
bool isDynamicDefault() const
Returns a flag indicating that the property's value has been set programmatically,...
Definition Property.cpp:382
void setIsDynamicDefault(const bool &flag)
Set or clear the flag indicating whether or not the property's value has been set programmatically.
Definition Property.cpp:387
unsigned int direction() const
returns the direction of the property
Definition Property.h:177
const std::string & documentation() const
Get the property's documentation string.
Definition Property.cpp:78
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:63
virtual std::string getDefault() const =0
Get the default value for the property which is the value the property was initialised with.
virtual std::string value() const =0
Returns the value of the property as a string.
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:24
constexpr long EMPTY_LONG() noexcept
Returns what we consider an "empty" long within a property.
Definition EmptyValues.h:30
constexpr double EMPTY_DBL() noexcept
Returns what we consider an "empty" double within a property.
Definition EmptyValues.h:42
@ Output
An output workspace.
Definition Property.h:55