Mantid
Loading...
Searching...
No Matches
AlgorithmPropertiesWidget.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 +
8
18
19#include <QCoreApplication>
20#include <QGridLayout>
21#include <QGroupBox>
22#include <QScrollArea>
23
24#include <algorithm>
25#include <utility>
26
27#include <vector>
28
29using namespace Mantid::Kernel;
34
35namespace MantidQt::API {
36
37//----------------------------------------------------------------------------------------------
41 : QWidget(parent), m_algoName(""), m_algo(), m_errors(nullptr), m_inputHistory(nullptr) {
42 // Create the grid layout that will have all the widgets
43 m_inputGrid = new QGridLayout;
44
45 // Create the viewport that holds only the grid layout
46 m_viewport = new QWidget(this);
47
48 // Put everything in a vertical box and put it inside the m_scroll area
49 auto *mainLay = new QVBoxLayout();
50 m_viewport->setLayout(mainLay);
51 // The property boxes
52 mainLay->addLayout(m_inputGrid);
53 // Add a stretchy item to allow the properties grid to be top-aligned
54 mainLay->addStretch(1);
55
56 // Create a m_scroll area for the (rare) occasion when an algorithm has
57 // so many properties it won't fit on the screen
58 m_scroll = new QScrollArea(this);
59 m_scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
60 m_scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
61 m_scroll->setWidget(m_viewport);
62 m_scroll->setWidgetResizable(true);
63 m_scroll->setAlignment(Qt::Alignment(Qt::AlignLeft & Qt::AlignTop));
64
65 // Add a layout for the whole widget, containing just the m_scroll area
66 auto *dialog_layout = new QVBoxLayout();
67 dialog_layout->addWidget(m_scroll);
68 setLayout(dialog_layout);
69
70 this->initLayout();
71
72 // Wide widgets inside the QScrollArea do not cause the dialog to grow
73 // and so can be cut off. Force a minimum width
74 setObjectName("AlgorithmPropertiesWidget");
75 setStyleSheet("#AlgorithmPropertiesWidget {min-width: 25em;}");
76}
77
78//----------------------------------------------------------------------------------------------
82
83//----------------------------------------------------------------------------------------------
92
93//----------------------------------------------------------------------------------------------
96
97//----------------------------------------------------------------------------------------------
102 if (!algo)
103 return;
104 saveInput();
105 m_algo = algo;
106 m_algoName = QString::fromStdString(m_algo->name());
107 this->initLayout();
108}
109
110//----------------------------------------------------------------------------------------------
113
118 FrameworkManager::Instance();
119 m_algoName = std::move(name);
120 try {
121 Algorithm_sptr alg = AlgorithmManager::Instance().createUnmanaged(m_algoName.toStdString());
122 alg->initialize();
123
124 // Set the algorithm ptr. This will redo the layout
125 this->setAlgorithm(alg);
126 } catch (std::runtime_error &) {
127 }
128}
129
130//---------------------------------------------------------------------------------------------------------------
132void AlgorithmPropertiesWidget::addEnabledAndDisableLists(const QStringList &enabled, const QStringList &disabled) {
133 this->m_enabled = enabled;
134 this->m_disabled = disabled;
135}
136
137//---------------------------------------------------------------------------------------------------------------
140
141//---------------------------------------------------------------------------------------------------------------
143bool AlgorithmPropertiesWidget::hasInputWS(const std::vector<Property *> &prop_list) const {
144 // For any algorithm that does not have any input workspaces,
145 // we do not want to render the 'replace input workspace button'.
146 // Do a scan to check.
147 std::vector<Property *>::const_iterator pEnd = prop_list.end();
148 for (std::vector<Property *>::const_iterator pIter = prop_list.begin(); pIter != pEnd; ++pIter) {
149 Property *prop = *pIter;
150 if (prop->direction() == Direction::Input && dynamic_cast<IWorkspaceProperty *>(prop)) {
151 return true;
152 }
153 }
154 return false;
155}
156
157//---------------------------------------------------------------------------------------------------------------
162 if (!getAlgorithm())
163 return;
164
165 // Delete PropertyWidgets first. This prevents a use-after-free / pure-virtual-call crash
166 // when the event loop later delivers a stale DeferredDelete event
167 for (auto &propWidget : m_propWidgets)
168 propWidget->deleteLater();
169
170 // Now drain the grid of any remaining widgets and layouts
171 QLayoutItem *child;
172 while ((child = m_inputGrid->takeAt(0)) != nullptr) {
173 if (child->widget())
174 child->widget()->deleteLater();
175
176 delete child;
177 }
178
179 QCoreApplication::processEvents();
180 m_propWidgets.clear();
181
182 // Create a grid of properties if there are any available
183 const std::vector<Property *> &prop_list = getAlgorithm()->getProperties();
184 bool hasInputWS_ = hasInputWS(prop_list);
185
186 if (!prop_list.empty()) {
187 // Put the property boxes in a grid
189
190 std::string group = "";
191
192 // Each property is on its own row
193 int row = 0;
194
195 for (auto prop : prop_list) {
196 QString propName = QString::fromStdString(prop->name());
197
198 // Are we entering a new group?
199 if (prop->getGroup() != group) {
200 group = prop->getGroup();
201
202 if (group == "") {
203 // Return to the original grid
205 } else {
206 // Make a groupbox with a border and a light background
207 QGroupBox *grpBox = new QGroupBox(QString::fromStdString(group));
208 grpBox->setAutoFillBackground(true);
209 grpBox->setStyleSheet("QGroupBox { border: 1px solid gray; border-radius: 4px; "
210 "font-weight: bold; margin-top: 4px; margin-bottom: 4px; "
211 "padding-top: 16px; }"
212 "QGroupBox::title { background-color: transparent; "
213 "subcontrol-position: top center; padding-top:4px; "
214 "padding-bottom:4px; } ");
215 QPalette pal = grpBox->palette();
216 pal.setColor(grpBox->backgroundRole(), pal.alternateBase().color());
217 grpBox->setPalette(pal);
218
219 // Put the frame in the main grid
220 m_inputGrid->addWidget(grpBox, row, 0, 1, 4);
221
222 m_groupWidgets[QString::fromStdString(group)] = grpBox;
223
224 // Make a layout in the grp box
225 m_currentGrid = new QGridLayout;
226 grpBox->setLayout(m_currentGrid);
227
228 row++;
229 }
230 }
231
232 // Only accept input for output properties or workspace properties
233 bool isWorkspaceProp(dynamic_cast<IWorkspaceProperty *>(prop));
234 if (prop->direction() == Direction::Output && !isWorkspaceProp)
235 continue;
236
237 // Create the appropriate widget at this row in the grid.
239
240 // Set the previous input value, if any
241 if (m_inputHistory) {
242 QString oldValue = m_inputHistory->previousInput(m_algoName, propName);
243 // Empty string if not found. This means use the default value.
244 if (!oldValue.isEmpty()) {
245 auto error = prop->setValue(oldValue.toStdString());
246 // TODO: [Known defect]: this does not match the initialization sequence
247 // in `AlgorithmDialog`. In the `AlgorithmDialog` case, the 'valueChanged' SIGNAL
248 // will already have been connected at the point the previous values are set.
249 // By implication: this initialization will not initialize properties
250 // with dynamic-default values correctly.
251 // Since at present this clause is not actually executed anywhere in the codebase.
252 // WHEN this clause is used, this issue should be fixed!
253 widget->setError(QString::fromStdString(error));
254 widget->setPrevious_isDynamicDefault(false);
255 widget->setPreviousValue(oldValue);
256 }
257 }
258
259 m_propWidgets[propName] = widget;
260
261 // Whenever the value changes in the widget, this fires propertyChanged()
262 connect(widget, SIGNAL(valueChanged(const QString &)), this, SLOT(propertyChanged(const QString &)));
263
264 // For clicking the "Replace Workspace" button (if any)
265 connect(widget, SIGNAL(replaceWorkspaceName(const QString &)), this, SLOT(replaceWSClicked(const QString &)));
266
267 // Only show the "Replace Workspace" button if the algorithm has an input
268 // workspace.
269 if (hasInputWS_ && !prop->disableReplaceWSButton())
270 widget->addReplaceWSButton();
271
272 ++row;
273 } //(end for each property)
274
275 } // (there are properties)
276}
277
278//--------------------------------------------------------------------------------------
283void AlgorithmPropertiesWidget::propertyChanged(const QString &changedPropName) {
284 this->hideOrDisableProperties(changedPropName);
285}
286
288 Mantid::Kernel::Property const *const property = candidate->getProperty();
289 const std::string &propertyName = property->name();
290 return propertyName == "InputWorkspace" || propertyName == "LHSWorkspace";
291}
292
293//-------------------------------------------------------------------------------------------------
298void AlgorithmPropertiesWidget::replaceWSClicked(const QString &propName) {
299 if (m_propWidgets.contains(propName)) {
300 PropertyWidget *propWidget = m_propWidgets[propName];
301 if (propWidget) {
302 using CollectionOfPropertyWidget = std::vector<PropertyWidget *>;
303 CollectionOfPropertyWidget candidateReplacementSources;
304 // Find the name to put in the spot
305 for (auto it = m_propWidgets.begin(); it != m_propWidgets.end(); it++) {
306 // Only look at workspace properties
307 PropertyWidget *otherWidget = it.value();
308 Property *prop = it.value()->getProperty();
309 const IWorkspaceProperty *wsProp = dynamic_cast<IWorkspaceProperty *>(prop);
310 if (otherWidget && wsProp) {
311 if (prop->direction() == Direction::Input) {
312 // Input workspace property. Get the text typed in.
313 QString wsName = otherWidget->getValue();
314 if (!wsName.isEmpty()) {
315 // Add the candidate to the list of candidates.
316 candidateReplacementSources.emplace_back(otherWidget);
317 }
318 }
319 }
320 }
321
322 // Choose from candidates, only do this if there are candidates to select
323 // from.
324 if (candidateReplacementSources.size() > 0) {
325 const auto selectedIt = std::find_if(candidateReplacementSources.cbegin(), candidateReplacementSources.cend(),
327 if (selectedIt != candidateReplacementSources.end()) {
328 // Use the InputWorkspace property called "InputWorkspace" or "LHSWorkspace" as the
329 // source for the OutputWorkspace.
330 propWidget->setValue((*selectedIt)->getValue());
331 } else {
332 // Take the first candidate if there are none called "InputWorkspace"
333 // as the source for the OutputWorkspace.
334 propWidget->setValue(candidateReplacementSources.front()->getValue());
335 }
336 propWidget->userEditedProperty();
337 }
338 }
339 }
340}
341
342//-------------------------------------------------------------------------------------------------
347 if (!prop)
348 throw std::runtime_error("`AlgorithmPropertiesWidget::isWidgetEnabled` called with null property pointer");
349 const std::string &propName = prop->name();
350
351 // Keep things enabled if requested
352 if (m_enabled.contains(QString::fromStdString(propName)))
353 return true;
354
359 if (m_disabled.contains(QString::fromStdString(propName)))
360 return false;
361
362 return m_algo->isPropertyEnabled(propName);
363}
364
365//-------------------------------------------------------------------------------------------------
371 if (!prop)
372 throw std::runtime_error("`AlgorithmPropertiesWidget::isWidgetVisible` called with null property pointer");
373 const std::string &propName = prop->name();
374
375 bool visible = m_algo->isPropertyVisible(propName);
376
377 // Always show properties that are in an error state
378 if (m_errors && !m_errors->value(QString::fromStdString(propName)).isEmpty())
379 visible = true;
380
381 return visible;
382}
383
384//-------------------------------------------------------------------------------------------------
392void AlgorithmPropertiesWidget::hideOrDisableProperties(const QString &changedPropName) {
393 // Apply `SetValueWhenProperty` or other `IPropertySettings` as appropriate.
394 auto const *changedPropWidget = !changedPropName.isEmpty() ? m_propWidgets[changedPropName] : nullptr;
395 for (auto &widget : m_propWidgets) {
396 Mantid::Kernel::Property *prop = widget->getProperty();
397 const QString propName = QString::fromStdString(prop->name());
398
399 auto const &settings = prop->getSettings();
400 if (!settings.empty()) {
401 // Dynamic PropertySettings objects allow a property to change
402 // validators. This removes the old widget and creates a new one
403 // instead.
404
405 for (auto const &setting : settings)
406 if (setting->isConditionChanged(m_algo.get(), changedPropName.toStdString())) {
407 if (setting->applyChanges(m_algo.get(), prop->name())) {
408 // WARNING: allow for the possibility that the current property has been replaced inside of `applyChanges`!
409 prop = m_algo->getPointerToProperty(propName.toStdString());
410
411 widget->setVisible(false);
412
413 // Create a new widget at the same position in the layout grid:
414 // since widget is a reference, this also replaces the `widget*` in `m_propWidgets`.
415 auto *oldWidget = widget;
416 int row = widget->getGridRow();
417 QGridLayout *layout = widget->getGridLayout();
418 widget = PropertyWidgetFactory::createWidget(prop, this, layout, row);
419 widget->transferHistoryState(oldWidget, changedPropWidget);
420
421 // Delete the old widget
422 oldWidget->deleteLater();
423
424 // Whenever the value changes in the widget, this fires
425 // propertyChanged()
426 connect(widget, SIGNAL(valueChanged(const QString &)), this, SLOT(propertyChanged(const QString &)));
427 }
428 }
429 }
430 } // for each property
431
432 // set Visible and Enabled as appropriate
433 for (auto &widget : m_propWidgets) {
434 Mantid::Kernel::Property const *prop = widget->getProperty();
435
436 // Set the enabled and visible flags based on what the settings and validators say.
437 bool enabled = this->isWidgetEnabled(prop);
438 bool visible = this->isWidgetVisible(prop);
439
440 // Hide/disable the widget
441 widget->setEnabled(enabled);
442 widget->setVisible(visible);
443 } // for each property
444
445 this->repaint();
446}
447
448//-------------------------------------------------------------------------------------------------
453 if (m_inputHistory) {
454 for (auto pitr = m_propWidgets.begin(); pitr != m_propWidgets.end(); ++pitr) {
455 PropertyWidget const *widget = pitr.value();
456 auto const *prop = widget->getProperty();
457 QString const &propName = pitr.key();
458
459 // Normalize default values to empty string.
460 QString value = (prop->isDefault() || prop->isDynamicDefault() ? "" : widget->getValue());
461
462 // save the value
463 m_inputHistory->storeNewValue(m_algoName, QPair<QString, QString>(propName, value));
464 }
465 }
466}
467
468} // namespace MantidQt::API
std::string name
Definition Run.cpp:60
double value
The value of the point.
Definition FitMW.cpp:51
double error
This abstract class deals with the loading and saving of previous algorithm property values to/from M...
QString previousInput(const QString &algName, const QString &propName) const
Retrieve an old parameter value.
void storeNewValue(const QString &algName, const QPair< QString, QString > &property)
Update the old values that are stored here.
QScrollArea * m_scroll
Scroll area containing the viewport.
bool hasInputWS(const std::vector< Mantid::Kernel::Property * > &prop_list) const
Check if there is any input workspace in the properties list.
void initLayout()
Create the layout for this dialog.
void replaceWSClicked(const QString &propName)
Replace WS button was clicked.
MantidQt::API::AbstractAlgorithmInputHistory * m_inputHistory
History of inputs to the algorithm.
Mantid::API::IAlgorithm_sptr m_algo
Pointer to the algorithm to view.
QStringList m_enabled
A list of property names that are FORCED to stay enabled.
QGridLayout * m_inputGrid
The grid widget containing the input boxes.
void setAlgorithmName(QString name)
Set the algorithm to view using its name.
bool isWidgetEnabled(const Mantid::Kernel::Property *prop) const
Check if the control should be enabled for this property.
QWidget * m_viewport
Viewport containing the grid of property widgets.
void shareErrorsMap(const QHash< QString, QString > &errors)
Share the errors map with the parent dialog.
QHash< QString, QGroupBox * > m_groupWidgets
Mapping between group and it's dynamically created widget.
void propertyChanged(const QString &changedPropName)
Any property changed.
void setAlgorithm(const Mantid::API::IAlgorithm_sptr &algo)
Directly set the algorithm to view.
bool isWidgetVisible(const Mantid::Kernel::Property *prop) const
Compute if the control should be visible for this property based on settings and error state.
void hideOrDisableProperties(const QString &changedPropName="")
Go through all the properties, and check their settings in order to implement any changes dependent o...
void addEnabledAndDisableLists(const QStringList &enabled, const QStringList &disabled)
Sets the properties to force as enabled/disabled.
QHash< QString, QString > const * m_errors
A map where key = property name; value = any error for this property (i.e.
QHash< QString, PropertyWidget * > m_propWidgets
Each dynamically created PropertyWidget.
void saveInput()
When closing or changing algorithm, this saves the input history to QSettings.
QStringList m_disabled
A list of property names that are FORCED to stay disabled.
QGridLayout * m_currentGrid
The current grid widget for sub-boxes.
AlgorithmPropertiesWidget(QWidget *parent=nullptr)
Constructor.
~AlgorithmPropertiesWidget() override
Destructor.
void setInputHistory(MantidQt::API::AbstractAlgorithmInputHistory *inputHistory)
Sets the AlgorithmInputHistoryImpl object holding all histories.
static PropertyWidget * createWidget(Mantid::Kernel::Property *prop, QWidget *parent=nullptr, QGridLayout *layout=nullptr, int row=-1)
Create the appropriate PropertyWidget for the given Property.
Base class for widgets that will set Mantid::Kernel::Property* types.
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 addReplaceWSButton()
Create and show the "Replace WS" button.
void setError(const QString &error)
Externally set an error string to display in the validator.
const Mantid::Kernel::Property * getProperty() const
void setPrevious_isDynamicDefault(bool flag)
Set the isDynamicDefault flag associated with the previously-entered value.
void userEditedProperty()
To be called when a user edits a property, as opposed to one being set programmatically.
void setPreviousValue(const QString &previousValue)
Set this widget's previously-entered value.
An interface that is implemented by WorkspaceProperty.
Base class for properties.
Definition Property.h:94
std::vector< std::unique_ptr< IPropertySettings const > > const & getSettings() const
Definition Property.cpp:113
unsigned int direction() const
returns the direction of the property
Definition Property.h:176
const std::string & name() const
Get the property's name.
Definition Property.cpp:63
virtual std::string value() const =0
Returns the value of the property as a string.
bool isCalledInputWorkspaceOrLHSWorkspace(PropertyWidget *const candidate)
std::shared_ptr< IAlgorithm > IAlgorithm_sptr
shared pointer to Mantid::API::IAlgorithm
Mantid::Kernel::SingletonHolder< FrameworkManagerImpl > FrameworkManager
Mantid::Kernel::SingletonHolder< AlgorithmManagerImpl > AlgorithmManager
std::shared_ptr< Algorithm > Algorithm_sptr
Typedef for a shared pointer to an Algorithm.
Definition Algorithm.h:52
@ Input
An input workspace.
Definition Property.h:53
@ Output
An output workspace.
Definition Property.h:54