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 all widgets in the layout
166 QLayoutItem *child;
167 while ((child = m_inputGrid->takeAt(0)) != nullptr) {
168 if (child->widget())
169 child->widget()->deleteLater();
170
171 delete child;
172 }
173
174 // This also deletes the PropertyWidget, which does not actually
175 // contain the sub-widgets because they are shared in the grid layout
176 for (auto &propWidget : m_propWidgets)
177 propWidget->deleteLater();
178 QCoreApplication::processEvents();
179 m_propWidgets.clear();
180
181 // Create a grid of properties if there are any available
182 const std::vector<Property *> &prop_list = getAlgorithm()->getProperties();
183 bool hasInputWS_ = hasInputWS(prop_list);
184
185 if (!prop_list.empty()) {
186 // Put the property boxes in a grid
188
189 std::string group = "";
190
191 // Each property is on its own row
192 int row = 0;
193
194 for (auto prop : prop_list) {
195 QString propName = QString::fromStdString(prop->name());
196
197 // Are we entering a new group?
198 if (prop->getGroup() != group) {
199 group = prop->getGroup();
200
201 if (group == "") {
202 // Return to the original grid
204 } else {
205 // Make a groupbox with a border and a light background
206 QGroupBox *grpBox = new QGroupBox(QString::fromStdString(group));
207 grpBox->setAutoFillBackground(true);
208 grpBox->setStyleSheet("QGroupBox { border: 1px solid gray; border-radius: 4px; "
209 "font-weight: bold; margin-top: 4px; margin-bottom: 4px; "
210 "padding-top: 16px; }"
211 "QGroupBox::title { background-color: transparent; "
212 "subcontrol-position: top center; padding-top:4px; "
213 "padding-bottom:4px; } ");
214 QPalette pal = grpBox->palette();
215 pal.setColor(grpBox->backgroundRole(), pal.alternateBase().color());
216 grpBox->setPalette(pal);
217
218 // Put the frame in the main grid
219 m_inputGrid->addWidget(grpBox, row, 0, 1, 4);
220
221 m_groupWidgets[QString::fromStdString(group)] = grpBox;
222
223 // Make a layout in the grp box
224 m_currentGrid = new QGridLayout;
225 grpBox->setLayout(m_currentGrid);
226
227 row++;
228 }
229 }
230
231 // Only accept input for output properties or workspace properties
232 bool isWorkspaceProp(dynamic_cast<IWorkspaceProperty *>(prop));
233 if (prop->direction() == Direction::Output && !isWorkspaceProp)
234 continue;
235
236 // Create the appropriate widget at this row in the grid.
238
239 // Set the previous input value, if any
240 if (m_inputHistory) {
241 QString oldValue = m_inputHistory->previousInput(m_algoName, propName);
242 // Empty string if not found. This means use the default value.
243 if (!oldValue.isEmpty()) {
244 auto error = prop->setValue(oldValue.toStdString());
245 // TODO: [Known defect]: this does not match the initialization sequence
246 // in `AlgorithmDialog`. In the `AlgorithmDialog` case, the 'valueChanged' SIGNAL
247 // will already have been connected at the point the previous values are set.
248 // By implication: this initialization will not initialize properties
249 // with dynamic-default values correctly.
250 // Since at present this clause is not actually executed anywhere in the codebase.
251 // WHEN this clause is used, this issue should be fixed!
252 widget->setError(QString::fromStdString(error));
253 widget->setPrevious_isDynamicDefault(false);
254 widget->setPreviousValue(oldValue);
255 }
256 }
257
258 m_propWidgets[propName] = widget;
259
260 // Whenever the value changes in the widget, this fires propertyChanged()
261 connect(widget, SIGNAL(valueChanged(const QString &)), this, SLOT(propertyChanged(const QString &)));
262
263 // For clicking the "Replace Workspace" button (if any)
264 connect(widget, SIGNAL(replaceWorkspaceName(const QString &)), this, SLOT(replaceWSClicked(const QString &)));
265
266 // Only show the "Replace Workspace" button if the algorithm has an input
267 // workspace.
268 if (hasInputWS_ && !prop->disableReplaceWSButton())
269 widget->addReplaceWSButton();
270
271 ++row;
272 } //(end for each property)
273
274 } // (there are properties)
275}
276
277//--------------------------------------------------------------------------------------
282void AlgorithmPropertiesWidget::propertyChanged(const QString &changedPropName) {
283 this->hideOrDisableProperties(changedPropName);
284}
285
287 Mantid::Kernel::Property const *const property = candidate->getProperty();
288 const std::string &propertyName = property->name();
289 return propertyName == "InputWorkspace" || propertyName == "LHSWorkspace";
290}
291
292//-------------------------------------------------------------------------------------------------
297void AlgorithmPropertiesWidget::replaceWSClicked(const QString &propName) {
298 if (m_propWidgets.contains(propName)) {
299 PropertyWidget *propWidget = m_propWidgets[propName];
300 if (propWidget) {
301 using CollectionOfPropertyWidget = std::vector<PropertyWidget *>;
302 CollectionOfPropertyWidget candidateReplacementSources;
303 // Find the name to put in the spot
304 for (auto it = m_propWidgets.begin(); it != m_propWidgets.end(); it++) {
305 // Only look at workspace properties
306 PropertyWidget *otherWidget = it.value();
307 Property *prop = it.value()->getProperty();
308 const IWorkspaceProperty *wsProp = dynamic_cast<IWorkspaceProperty *>(prop);
309 if (otherWidget && wsProp) {
310 if (prop->direction() == Direction::Input) {
311 // Input workspace property. Get the text typed in.
312 QString wsName = otherWidget->getValue();
313 if (!wsName.isEmpty()) {
314 // Add the candidate to the list of candidates.
315 candidateReplacementSources.emplace_back(otherWidget);
316 }
317 }
318 }
319 }
320
321 // Choose from candidates, only do this if there are candidates to select
322 // from.
323 if (candidateReplacementSources.size() > 0) {
324 const auto selectedIt = std::find_if(candidateReplacementSources.cbegin(), candidateReplacementSources.cend(),
326 if (selectedIt != candidateReplacementSources.end()) {
327 // Use the InputWorkspace property called "InputWorkspace" or "LHSWorkspace" as the
328 // source for the OutputWorkspace.
329 propWidget->setValue((*selectedIt)->getValue());
330 } else {
331 // Take the first candidate if there are none called "InputWorkspace"
332 // as the source for the OutputWorkspace.
333 propWidget->setValue(candidateReplacementSources.front()->getValue());
334 }
335 propWidget->userEditedProperty();
336 }
337 }
338 }
339}
340
341//-------------------------------------------------------------------------------------------------
346 if (!prop)
347 throw std::runtime_error("`AlgorithmPropertiesWidget::isWidgetEnabled` called with null property pointer");
348 const std::string &propName = prop->name();
349
350 // Keep things enabled if requested
351 if (m_enabled.contains(QString::fromStdString(propName)))
352 return true;
353
358 if (m_disabled.contains(QString::fromStdString(propName)))
359 return false;
360
361 return m_algo->isPropertyEnabled(propName);
362}
363
364//-------------------------------------------------------------------------------------------------
370 if (!prop)
371 throw std::runtime_error("`AlgorithmPropertiesWidget::isWidgetVisible` called with null property pointer");
372 const std::string &propName = prop->name();
373
374 bool visible = m_algo->isPropertyVisible(propName);
375
376 // Always show properties that are in an error state
377 if (m_errors && !m_errors->value(QString::fromStdString(propName)).isEmpty())
378 visible = true;
379
380 return visible;
381}
382
383//-------------------------------------------------------------------------------------------------
391void AlgorithmPropertiesWidget::hideOrDisableProperties(const QString &changedPropName) {
392 // Apply `SetValueWhenProperty` or other `IPropertySettings` as appropriate.
393 auto const *changedPropWidget = !changedPropName.isEmpty() ? m_propWidgets[changedPropName] : nullptr;
394 for (auto &widget : m_propWidgets) {
395 Mantid::Kernel::Property *prop = widget->getProperty();
396 const QString propName = QString::fromStdString(prop->name());
397
398 auto const &settings = prop->getSettings();
399 if (!settings.empty()) {
400 // Dynamic PropertySettings objects allow a property to change
401 // validators. This removes the old widget and creates a new one
402 // instead.
403
404 for (auto const &setting : settings)
405 if (setting->isConditionChanged(m_algo.get(), changedPropName.toStdString())) {
406 if (setting->applyChanges(m_algo.get(), prop->name())) {
407 // WARNING: allow for the possibility that the current property has been replaced inside of `applyChanges`!
408 prop = m_algo->getPointerToProperty(propName.toStdString());
409
410 widget->setVisible(false);
411
412 // Create a new widget at the same position in the layout grid:
413 // since widget is a reference, this also replaces the `widget*` in `m_propWidgets`.
414 auto *oldWidget = widget;
415 int row = widget->getGridRow();
416 QGridLayout *layout = widget->getGridLayout();
417 widget = PropertyWidgetFactory::createWidget(prop, this, layout, row);
418 widget->transferHistoryState(oldWidget, changedPropWidget);
419
420 // Delete the old widget
421 oldWidget->deleteLater();
422
423 // Whenever the value changes in the widget, this fires
424 // propertyChanged()
425 connect(widget, SIGNAL(valueChanged(const QString &)), this, SLOT(propertyChanged(const QString &)));
426 }
427 }
428 }
429 } // for each property
430
431 // set Visible and Enabled as appropriate
432 for (auto &widget : m_propWidgets) {
433 Mantid::Kernel::Property const *prop = widget->getProperty();
434
435 // Set the enabled and visible flags based on what the settings and validators say.
436 bool enabled = this->isWidgetEnabled(prop);
437 bool visible = this->isWidgetVisible(prop);
438
439 // Hide/disable the widget
440 widget->setEnabled(enabled);
441 widget->setVisible(visible);
442 } // for each property
443
444 this->repaint();
445}
446
447//-------------------------------------------------------------------------------------------------
452 if (m_inputHistory) {
453 for (auto pitr = m_propWidgets.begin(); pitr != m_propWidgets.end(); ++pitr) {
454 PropertyWidget const *widget = pitr.value();
455 auto const *prop = widget->getProperty();
456 QString const &propName = pitr.key();
457
458 // Normalize default values to empty string.
459 QString value = (prop->isDefault() || prop->isDynamicDefault() ? "" : widget->getValue());
460
461 // save the value
462 m_inputHistory->storeNewValue(m_algoName, QPair<QString, QString>(propName, value));
463 }
464 }
465}
466
467} // 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