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
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 const *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 const *prop) {
102 return isValidPropertyValue(prop, "") || isValidPropertyValue(prop, prop->getDefault());
103}
104
115std::string createFieldPlaceholderText(Mantid::Kernel::Property const *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_lastValue(), m_error(), m_isOutputWsProp(false),
183 m_previousValue(), m_previous_isDynamicDefault(false), m_enteredValue(), m_entered_isDynamicDefault(false),
184 m_icons(), m_useHistory(true) {
185 if (!prop)
186 throw std::runtime_error("NULL Property passed to the PropertyWidget constructor.");
187 setObjectName(QString::fromStdString(prop->name()));
188
189 if (!m_gridLayout) {
190 // Create a LOCAL grid layout
191 m_gridLayout = new QGridLayout(this);
192
193 m_gridLayout->setSpacing(5);
194 this->setLayout(m_gridLayout);
195 m_row = 0;
196 m_parent = this;
197 } else {
198 // Use the parent of the provided QGridLayout when adding widgets
199 m_parent = parent;
200 // HACK - In this mode a property widget is not a true self-contained
201 // widget: it has no children
202 // of its own. By default, when added to a parent widget, it will be
203 // drawn invisble
204 // at the top left of the parent widget and obscure mouse clicks etc.
205 // The hack fix is to lower it down the visible stack.
206 this->lower();
207 }
208
209 auto *infoWidget = new QWidget();
210 infoWidget->setLayout(new QHBoxLayout(this));
211 infoWidget->layout()->setSpacing(1);
212 infoWidget->layout()->setContentsMargins(0, 0, 0, 0);
213 // `addLayout` sets parent of `infoWidget` correctly
214 m_gridLayout->addWidget(infoWidget, m_row, 4);
215
216 QMap<Info, QPair<QString, QString>> pathsAndToolTips;
217 pathsAndToolTips[RESTORE] =
218 QPair<QString, QString>(":/history.png", "This property had a previously-entered value. Click "
219 "to toggle it off and on.");
220 pathsAndToolTips[REPLACE] =
221 QPair<QString, QString>(":/replace.png", "A workspace with this name already exists and so will be overwritten.");
222 pathsAndToolTips[INVALID] = QPair<QString, QString>(":/invalid.png", "");
223
224 std::vector<Info> labelOrder = {RESTORE, REPLACE, INVALID};
225
226 for (const Info &info : labelOrder) {
227 const QString iconPath = pathsAndToolTips[info].first;
228 const QString toolTip = pathsAndToolTips[info].second;
229
230 auto icon = new ClickableLabel(this);
231 icon->setPixmap(QPixmap(iconPath).scaledToHeight(15));
232 icon->setVisible(false);
233 icon->setToolTip(toolTip);
234
235 infoWidget->layout()->addWidget(icon);
236 m_icons[info] = icon;
237 }
238
239 // Qt will have parented the `infoWidget` to `m_gridLayout`, and re-parented all of the icons to `infoWidget`.
240 // However, some end-user classes need to be able to completely recreate the `PropertyWidget`,
241 // so for this reason we explicitly delete these objects when this widget is destroyed.
242 connect(this, &QObject::destroyed, this, [infoWidget]() {
243 infoWidget->deleteLater(); // Safe deferred deletion
244 });
245
246 connect(m_icons[RESTORE], SIGNAL(clicked()), this, SLOT(toggleUseHistory()));
247
249 m_doc = QString::fromStdString(prop->documentation());
250
251 if (!isOptionalProperty(prop)) {
252 if (!m_doc.isEmpty())
253 m_doc += ".\n\n";
254 m_doc += "This property is required.";
255 }
256
257 if (prop->direction() == Direction::Output && dynamic_cast<IWorkspaceProperty *>(prop))
258 m_isOutputWsProp = true;
259}
260
264
270void PropertyWidget::setValue(const QString &value) {
271 // Set the value in the widget itself.
274
275 // Set the current value in the property, and check its validation.
276 // TODO: this method could have a much better name!
278
279 // Emit the `valueChanged` signal only when both the widget (and its property)
280 // have their _current_ values, _AND_ those values are valid!
281 if (m_error.isEmpty()) {
283 }
284}
285
291void PropertyWidget::setPreviousValue(const QString &previousValue) {
292 m_useHistory = true;
293 m_previousValue = previousValue;
295
296 // Handle input workspace options that have been set to a workspace
297 // previously, but where the workspace no longer exists.
298 if (getValue() != previousValue) {
299 m_previousValue = "";
301 }
302
303 // Once we've made the history icon visible, it will stay that way for
304 // the lifetime of the property widget.
305 if (!m_previousValue.isEmpty() && m_previousValue.toStdString() != m_prop->getDefault()) {
307 m_icons[RESTORE]->setVisible(true);
308 }
309}
310
320
334 QString value = QString::fromStdString(m_prop->value());
335 bool isDynamicDefault = m_prop->isDynamicDefault();
336
337 m_icons[RESTORE]->setVisible(other->m_icons[RESTORE]->isVisible());
338 if (!upstream) {
339 // This clause transfers the state details, while making the new
340 // property's state consistent with its new value.
341 m_previousValue = other->m_previousValue;
343 m_previous_isDynamicDefault = other->m_previous_isDynamicDefault;
344 m_enteredValue = (m_useHistory ? other->m_enteredValue : value);
345 m_entered_isDynamicDefault = (m_useHistory ? other->m_entered_isDynamicDefault : isDynamicDefault);
346 } else {
347 // This clause treats the dynamic-default case:
348 // the use-history state for a dynamic-default value tracks the upstream state.
349 m_useHistory = upstream->m_useHistory;
350 m_previousValue = (m_useHistory ? value : other->m_previousValue);
351 m_previous_isDynamicDefault = (m_useHistory ? isDynamicDefault : other->m_previous_isDynamicDefault);
352 m_enteredValue = (m_useHistory ? other->m_enteredValue : value);
353 m_entered_isDynamicDefault = (m_useHistory ? other->m_entered_isDynamicDefault : isDynamicDefault);
354 }
355
356 setUseHistoryIcon(m_useHistory, isDynamicDefault);
357}
358
365 QString userError(error);
366 if (userError.isEmpty()) {
367 // Show "*" icon if the value is invalid for this property.
368 QString value = this->getValue().trimmed();
369 // Use the default if empty
370 if (value.isEmpty())
371 value = QString::fromStdString(m_prop->getDefault());
372
373 try {
374 userError = QString::fromStdString(m_prop->setValue(value.toStdString()));
375 } catch (std::exception &err_details) {
376 userError = QString::fromLatin1(err_details.what());
377 }
378 }
379 this->setError(userError.trimmed());
380
381 m_icons[INVALID]->setVisible(!m_error.isEmpty());
382 m_icons[INVALID]->setToolTip(m_error);
383 // Show "!" icon if a workspace would be overwritten.
384 if (m_isOutputWsProp) {
385 const bool wsExists = Mantid::API::AnalysisDataService::Instance().doesExist(getValue().toStdString());
386 m_icons[REPLACE]->setVisible(wsExists);
387 }
388}
389
391void PropertyWidget::replaceWSButtonClicked() { emit replaceWorkspaceName(QString::fromStdString(m_prop->name())); }
392
397 // This will be caught by the GenericDialog.
398 emit valueChanged(QString::fromStdString(m_prop->name()));
399}
400
406 QString value = this->getValue();
407 if (value != m_lastValue) {
408 // DO NOTHING if the value has not changed =>
409 // ignore focus changes that don't correspond to value changes.
411
412 if (value != m_previousValue) {
413 m_useHistory = false;
415
416 // The user has edited the property => disable dynamic-default values.
419 } else {
420 m_useHistory = true;
422 }
423
425
426 // The property's value is validated and updated here:
428
429 // As in `setValue`:
430 // emit the `valueChanged` signal only when both the widget (and its property)
431 // have their _current_ values, _AND_ those values are valid!
432 if (m_error.isEmpty())
434 }
435}
436
451
461void PropertyWidget::setUseHistoryIcon(bool useHistory, bool isDynamicDefault) {
462 const QString iconPath = useHistory ? (isDynamicDefault ? ":/history_dynamic_default.png" : ":/history.png")
463 : (isDynamicDefault ? ":/history_off_dynamic_default.png" : ":/history_off.png");
464 m_icons[RESTORE]->setPixmap(QPixmap(iconPath).scaledToHeight(15));
465
466 // When any user-entered value exists:
467 // OR when a value is a dynamic-default value:
468 // make the use-history icons visible.
469 if (!m_enteredValue.isEmpty() || isDynamicDefault)
470 m_icons[RESTORE]->setVisible(true);
471}
472
479 // Don't re-create it if it already exists
481 return;
482
483 auto const *wsProp = dynamic_cast<IWorkspaceProperty *>(m_prop);
484 if (wsProp && (m_prop->direction() == Direction::Output)) {
485 m_replaceWSButton = new QPushButton(QIcon(":/data_replace.png"), "", m_parent);
486 // MG: There is no way with the QIcon class to actually ask what size it is
487 // so I had to hard
488 // code this number here to get it to a sensible size
489 m_replaceWSButton->setMaximumWidth(32);
490 // m_wsbtn_tracker[btn ] = 1;
491 m_replaceWSButton->setToolTip("Replace input workspace");
492 connect(m_replaceWSButton, SIGNAL(clicked()), this, SLOT(replaceWSButtonClicked()));
493 connect(m_replaceWSButton, SIGNAL(clicked()), this, SLOT(valueChangedSlot()));
494 m_widgets.push_back(m_replaceWSButton);
495 // Place in the grid on column 2.
496 m_gridLayout->addWidget(m_replaceWSButton, m_row, 2);
497 m_replaceWSButton->setVisible(true);
498 }
499}
500
505void PropertyWidget::setError(const QString &error) { m_error = error.trimmed(); }
506
510 for (auto &widget : m_widgets)
511 widget->setEnabled(val);
512 QWidget::setEnabled(val);
513}
514
518 for (auto &widget : m_widgets)
519 widget->setVisible(val);
520 QWidget::setVisible(val);
521}
522
532 if (!isOptionalProperty(prop)) {
533 auto font = label->font();
534 font.setBold(true);
535 label->setFont(font);
536 }
537}
538
548 field->setPlaceholderText(QString::fromStdString(createFieldPlaceholderText(prop)));
549}
550
551} // 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:48
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)
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 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:94
bool isDynamicDefault() const
Returns a flag indicating that the property's value has been set programmatically,...
Definition Property.cpp:380
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:385
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:76
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:61
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:54