Skip to content

Commit

Permalink
[processing] Cleanup handling of "iterate over source" buttons, move
Browse files Browse the repository at this point in the history
creation completely to widget wrapper
  • Loading branch information
nyalldawson committed Mar 24, 2020
1 parent 6038a7b commit 6e2c21e
Show file tree
Hide file tree
Showing 9 changed files with 124 additions and 72 deletions.
6 changes: 6 additions & 0 deletions python/core/auto_additions/qgsprocessingparameters.py
@@ -0,0 +1,6 @@
# The following has been generated automatically from src/core/processing/qgsprocessingparameters.h
# monkey patching scoped based enum
QgsProcessingFeatureSourceDefinition.Flag.FlagOverrideDefaultGeometryCheck.__doc__ = "If set, the default geometry check method (as dictated by QgsProcessingContext) will be overridden for this source"
QgsProcessingFeatureSourceDefinition.Flag.FlagCreateIndividualOutputPerInputFeature.__doc__ = "If set, every feature processed from this source will be placed into its own individually created output destination. Support for this flag depends on how an algorithm is executed."
QgsProcessingFeatureSourceDefinition.Flag.__doc__ = 'Flags which control source behavior.\n\n.. versionadded:: 3.14\n\n' + '* ``FlagOverrideDefaultGeometryCheck``: ' + QgsProcessingFeatureSourceDefinition.Flag.FlagOverrideDefaultGeometryCheck.__doc__ + '\n' + '* ``FlagCreateIndividualOutputPerInputFeature``: ' + QgsProcessingFeatureSourceDefinition.Flag.FlagCreateIndividualOutputPerInputFeature.__doc__
# --
Expand Up @@ -25,8 +25,16 @@ Encapsulates settings relating to a feature source input to a processing algorit
%End
public:

enum class Flag
{
FlagOverrideDefaultGeometryCheck,
FlagCreateIndividualOutputPerInputFeature,
};
typedef QFlags<QgsProcessingFeatureSourceDefinition::Flag> Flags;


QgsProcessingFeatureSourceDefinition( const QString &source = QString(), bool selectedFeaturesOnly = false, long long featureLimit = -1,
bool overrideDefaultGeometryCheck = false, QgsFeatureRequest::InvalidGeometryCheck geometryCheck = QgsFeatureRequest::GeometryAbortOnInvalid );
QgsProcessingFeatureSourceDefinition::Flags flags = 0, QgsFeatureRequest::InvalidGeometryCheck geometryCheck = QgsFeatureRequest::GeometryAbortOnInvalid );
%Docstring
Constructor for QgsProcessingFeatureSourceDefinition, accepting a static string ``source``.

Expand All @@ -35,12 +43,14 @@ If ``selectedFeaturesOnly`` is ``True``, then only selected features from the so
The optional ``featureLimit`` can be set to a value > 0 to place a hard limit on the maximum number
of features which will be read from the source.

If ``overrideDefaultGeometryCheck`` is ``True``, then the value of ``geometryCheck`` will override
The ``flags`` argument can be used to specify flags which dictate the source behavior.

If the QgsProcessingFeatureSourceDefinition.Flag.FlagOverrideDefaultGeometryCheck is set in ``flags``, then the value of ``geometryCheck`` will override
the default geometry check method (as dictated by :py:class:`QgsProcessingContext`) for this source.
%End

QgsProcessingFeatureSourceDefinition( const QgsProperty &source, bool selectedFeaturesOnly = false, long long featureLimit = -1,
bool overrideDefaultGeometryCheck = false, QgsFeatureRequest::InvalidGeometryCheck geometryCheck = QgsFeatureRequest::GeometryAbortOnInvalid );
QgsProcessingFeatureSourceDefinition::Flags flags = 0, QgsFeatureRequest::InvalidGeometryCheck geometryCheck = QgsFeatureRequest::GeometryAbortOnInvalid );
%Docstring
Constructor for QgsProcessingFeatureSourceDefinition, accepting a QgsProperty source.

Expand All @@ -49,7 +59,9 @@ If ``selectedFeaturesOnly`` is ``True``, then only selected features from the so
The optional ``featureLimit`` can be set to a value > 0 to place a hard limit on the maximum number
of features which will be read from the source.

If ``overrideDefaultGeometryCheck`` is ``True``, then the value of ``geometryCheck`` will override
The ``flags`` argument can be used to specify flags which dictate the source behavior.

If the QgsProcessingFeatureSourceDefinition.Flag.FlagOverrideDefaultGeometryCheck is set in ``flags``, then the value of ``geometryCheck`` will override
the default geometry check method (as dictated by :py:class:`QgsProcessingContext`) for this source.
%End

Expand All @@ -59,7 +71,7 @@ the default geometry check method (as dictated by :py:class:`QgsProcessingContex

long long featureLimit;

bool overrideDefaultGeometryCheck;
Flags flags;

QgsFeatureRequest::InvalidGeometryCheck geometryCheck;

Expand All @@ -71,6 +83,8 @@ the default geometry check method (as dictated by :py:class:`QgsProcessingContex

};

QFlags<QgsProcessingFeatureSourceDefinition::Flag> operator|(QgsProcessingFeatureSourceDefinition::Flag f1, QFlags<QgsProcessingFeatureSourceDefinition::Flag> f2);



class QgsProcessingOutputLayerDefinition
Expand Down
12 changes: 6 additions & 6 deletions python/plugins/processing/gui/AlgorithmDialog.py
Expand Up @@ -43,7 +43,8 @@
QgsProcessingAlgorithm,
QgsProcessingParameters,
QgsProxyProgressTask,
QgsTaskManager)
QgsTaskManager,
QgsProcessingFeatureSourceDefinition)
from qgis.gui import (QgsGui,
QgsMessageBar,
QgsProcessingAlgorithmDialogBase)
Expand Down Expand Up @@ -203,13 +204,12 @@ def runAlgorithm(self):
self.blockControlsWhileRunning()
self.setExecutedAnyResult(True)
self.cancelButton().setEnabled(False)
buttons = self.mainWidget().iterateButtons

self.iterateParam = None

for i in range(len(list(buttons.values()))):
button = list(buttons.values())[i]
if button.isChecked():
self.iterateParam = list(buttons.keys())[i]
for param in self.algorithm().parameterDefinitions():
if isinstance(parameters.get(param.name(), None), QgsProcessingFeatureSourceDefinition) and parameters[param.name()].flags & QgsProcessingFeatureSourceDefinition.Flag.FlagCreateIndividualOutputPerInputFeature:
self.iterateParam = param.name()
break

self.clearProgress()
Expand Down
26 changes: 0 additions & 26 deletions python/plugins/processing/gui/ParametersPanel.py
Expand Up @@ -85,7 +85,6 @@ def __init__(self, parent, alg, in_place=False):
self.outputWidgets = {}
self.checkBoxes = {}
self.dependentItems = {}
self.iterateButtons = {}

self.processing_context = createContext()

Expand Down Expand Up @@ -168,24 +167,6 @@ def initWidgets(self):
if is_python_wrapper:
widget.setToolTip(param.toolTip())

if isinstance(param, QgsProcessingParameterFeatureSource):
layout = QHBoxLayout()
layout.setSpacing(6)
layout.setMargin(0)
layout.addWidget(widget)
button = QToolButton()
icon = QIcon(os.path.join(pluginPath, 'images', 'iterate.png'))
button.setIcon(icon)
button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
button.setToolTip(self.tr('Iterate over this layer, creating a separate output for every feature in the layer'))
button.setCheckable(True)
layout.addWidget(button)
layout.setAlignment(button, Qt.AlignTop)
self.iterateButtons[param.name()] = button
button.toggled.connect(self.buttonToggled)
widget = QWidget()
widget.setLayout(layout)

label = None
if not is_python_wrapper:
label = wrapper.createWrappedLabel()
Expand Down Expand Up @@ -277,10 +258,3 @@ def setParameters(self, parameters):
else:
dest_widget = self.outputWidgets[param.name()]
dest_widget.setValue(parameters[param.name()])

def buttonToggled(self, value):
if value:
sender = self.sender()
for button in list(self.iterateButtons.values()):
if button is not sender:
button.setChecked(False)
31 changes: 29 additions & 2 deletions python/plugins/processing/gui/wrappers.py
Expand Up @@ -92,7 +92,9 @@
QPlainTextEdit,
QToolButton,
QWidget,
QSizePolicy
)
from qgis.PyQt.QtGui import QIcon
from qgis.gui import (
QgsGui,
QgsExpressionLineEdit,
Expand Down Expand Up @@ -129,6 +131,8 @@
DIALOG_BATCH = QgsProcessingGui.Batch
DIALOG_MODELER = QgsProcessingGui.Modeler

pluginPath = os.path.split(os.path.dirname(__file__))[0]


class InvalidParameterValue(Exception):
pass
Expand Down Expand Up @@ -1151,7 +1155,25 @@ def createWidget(self):

self.combo.valueChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
self.combo.triggerFileSelection.connect(self.selectFile)
return self.combo

layout = QHBoxLayout()
layout.setSpacing(6)
layout.setMargin(0)
layout.addWidget(self.combo)
self.iterate_button = QToolButton()
icon = QIcon(os.path.join(pluginPath, 'images', 'iterate.png'))
self.iterate_button.setIcon(icon)
self.iterate_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
self.iterate_button.setToolTip(
self.tr('Iterate over this layer, creating a separate output for every feature in the layer'))
self.iterate_button.setCheckable(True)
layout.addWidget(self.iterate_button)
layout.setAlignment(self.iterate_button, Qt.AlignTop)

widget = QWidget()
widget.setLayout(layout)
return widget

elif self.dialogType == DIALOG_BATCH:
widget = BatchInputSelectionPanel(self.parameterDefinition(), self.row, self.col, self.dialog)
widget.valueChanged.connect(lambda: self.widgetValueHasChanged.emit(self))
Expand Down Expand Up @@ -1214,7 +1236,12 @@ def setValue(self, value):

def value(self):
if self.dialogType == DIALOG_STANDARD:
return self.combo.value()
v = self.combo.value()
if self.iterate_button.isChecked():
if not isinstance(v, QgsProcessingFeatureSourceDefinition):
v = QgsProcessingFeatureSourceDefinition(v)
v.flags = v.flags | QgsProcessingFeatureSourceDefinition.Flag.FlagCreateIndividualOutputPerInputFeature
return v
elif self.dialogType == DIALOG_BATCH:
return self.widget.getValue()
else:
Expand Down
24 changes: 18 additions & 6 deletions src/core/processing/qgsprocessingparameters.cpp
Expand Up @@ -4641,19 +4641,31 @@ QString QgsProcessingParameterFeatureSource::valueAsPythonString( const QVariant
geometryCheckString = QStringLiteral( "QgsFeatureRequest.GeometryAbortOnInvalid" );
break;
}

QStringList flags;
QString flagString;
if ( fromVar.flags & QgsProcessingFeatureSourceDefinition::Flag::FlagOverrideDefaultGeometryCheck )
flags << QStringLiteral( "QgsProcessingFeatureSourceDefinition.Flag.FlagOverrideDefaultGeometryCheck" );
if ( fromVar.flags & QgsProcessingFeatureSourceDefinition::Flag::FlagCreateIndividualOutputPerInputFeature )
flags << QStringLiteral( "QgsProcessingFeatureSourceDefinition.Flag.FlagCreateIndividualOutputPerInputFeature" );
if ( !flags.empty() )
flagString = flags.join( QStringLiteral( " | " ) );
else
flagString = QStringLiteral( "None" );

if ( fromVar.source.propertyType() == QgsProperty::StaticProperty )
{
QString layerString = fromVar.source.staticValue().toString();
// prefer to use layer source instead of id if possible (since it's persistent)
if ( QgsVectorLayer *layer = qobject_cast< QgsVectorLayer * >( QgsProcessingUtils::mapLayerFromString( layerString, context, true, QgsProcessingUtils::LayerHint::Vector ) ) )
layerString = layer->source();

if ( fromVar.selectedFeaturesOnly || fromVar.featureLimit != -1 || fromVar.overrideDefaultGeometryCheck )
if ( fromVar.selectedFeaturesOnly || fromVar.featureLimit != -1 || fromVar.flags )
{
return QStringLiteral( "QgsProcessingFeatureSourceDefinition('%1', selectedFeaturesOnly=%2, featureLimit=%3, overrideDefaultGeometryCheck=%4, geometryCheck=%5)" ).arg( layerString,
return QStringLiteral( "QgsProcessingFeatureSourceDefinition('%1', selectedFeaturesOnly=%2, featureLimit=%3, flags=%4, geometryCheck=%5)" ).arg( layerString,
fromVar.selectedFeaturesOnly ? QStringLiteral( "True" ) : QStringLiteral( "False" ),
QString::number( fromVar.featureLimit ),
fromVar.overrideDefaultGeometryCheck ? QStringLiteral( "True" ) : QStringLiteral( "False" ),
flagString,
geometryCheckString );
}
else
Expand All @@ -4663,13 +4675,13 @@ QString QgsProcessingParameterFeatureSource::valueAsPythonString( const QVariant
}
else
{
if ( fromVar.selectedFeaturesOnly || fromVar.featureLimit != -1 || fromVar.overrideDefaultGeometryCheck )
if ( fromVar.selectedFeaturesOnly || fromVar.featureLimit != -1 || fromVar.flags )
{
return QStringLiteral( "QgsProcessingFeatureSourceDefinition(QgsProperty.fromExpression('%1'), selectedFeaturesOnly=%2, featureLimit=%3, overrideDefaultGeometryCheck=%4, geometryCheck=%5)" )
return QStringLiteral( "QgsProcessingFeatureSourceDefinition(QgsProperty.fromExpression('%1'), selectedFeaturesOnly=%2, featureLimit=%3, flags=%4, geometryCheck=%5)" )
.arg( fromVar.source.asExpression(),
fromVar.selectedFeaturesOnly ? QStringLiteral( "True" ) : QStringLiteral( "False" ),
QString::number( fromVar.featureLimit ),
fromVar.overrideDefaultGeometryCheck ? QStringLiteral( "True" ) : QStringLiteral( "False" ),
flagString,
geometryCheckString );
}
else
Expand Down
39 changes: 27 additions & 12 deletions src/core/processing/qgsprocessingparameters.h
Expand Up @@ -55,6 +55,17 @@ class CORE_EXPORT QgsProcessingFeatureSourceDefinition
{
public:

/**
* Flags which control source behavior.
* \since QGIS 3.14
*/
enum class Flag
{
FlagOverrideDefaultGeometryCheck = 1 << 0, //!< If set, the default geometry check method (as dictated by QgsProcessingContext) will be overridden for this source
FlagCreateIndividualOutputPerInputFeature = 1 << 1, //!< If set, every feature processed from this source will be placed into its own individually created output destination. Support for this flag depends on how an algorithm is executed.
};
Q_DECLARE_FLAGS( Flags, Flag )

/**
* Constructor for QgsProcessingFeatureSourceDefinition, accepting a static string \a source.
*
Expand All @@ -63,15 +74,17 @@ class CORE_EXPORT QgsProcessingFeatureSourceDefinition
* The optional \a featureLimit can be set to a value > 0 to place a hard limit on the maximum number
* of features which will be read from the source.
*
* If \a overrideDefaultGeometryCheck is TRUE, then the value of \a geometryCheck will override
* The \a flags argument can be used to specify flags which dictate the source behavior.
*
* If the QgsProcessingFeatureSourceDefinition::Flag::FlagOverrideDefaultGeometryCheck is set in \a flags, then the value of \a geometryCheck will override
* the default geometry check method (as dictated by QgsProcessingContext) for this source.
*/
QgsProcessingFeatureSourceDefinition( const QString &source = QString(), bool selectedFeaturesOnly = false, long long featureLimit = -1,
bool overrideDefaultGeometryCheck = false, QgsFeatureRequest::InvalidGeometryCheck geometryCheck = QgsFeatureRequest::GeometryAbortOnInvalid )
QgsProcessingFeatureSourceDefinition::Flags flags = nullptr, QgsFeatureRequest::InvalidGeometryCheck geometryCheck = QgsFeatureRequest::GeometryAbortOnInvalid )
: source( QgsProperty::fromValue( source ) )
, selectedFeaturesOnly( selectedFeaturesOnly )
, featureLimit( featureLimit )
, overrideDefaultGeometryCheck( overrideDefaultGeometryCheck )
, flags( flags )
, geometryCheck( geometryCheck )
{}

Expand All @@ -83,15 +96,17 @@ class CORE_EXPORT QgsProcessingFeatureSourceDefinition
* The optional \a featureLimit can be set to a value > 0 to place a hard limit on the maximum number
* of features which will be read from the source.
*
* If \a overrideDefaultGeometryCheck is TRUE, then the value of \a geometryCheck will override
* The \a flags argument can be used to specify flags which dictate the source behavior.
*
* If the QgsProcessingFeatureSourceDefinition::Flag::FlagOverrideDefaultGeometryCheck is set in \a flags, then the value of \a geometryCheck will override
* the default geometry check method (as dictated by QgsProcessingContext) for this source.
*/
QgsProcessingFeatureSourceDefinition( const QgsProperty &source, bool selectedFeaturesOnly = false, long long featureLimit = -1,
bool overrideDefaultGeometryCheck = false, QgsFeatureRequest::InvalidGeometryCheck geometryCheck = QgsFeatureRequest::GeometryAbortOnInvalid )
QgsProcessingFeatureSourceDefinition::Flags flags = nullptr, QgsFeatureRequest::InvalidGeometryCheck geometryCheck = QgsFeatureRequest::GeometryAbortOnInvalid )
: source( source )
, selectedFeaturesOnly( selectedFeaturesOnly )
, featureLimit( featureLimit )
, overrideDefaultGeometryCheck( overrideDefaultGeometryCheck )
, flags( flags )
, geometryCheck( geometryCheck )
{}

Expand All @@ -114,17 +129,16 @@ class CORE_EXPORT QgsProcessingFeatureSourceDefinition
long long featureLimit = -1;

/**
* TRUE if the default geometry check method (as dictated by QgsProcessingContext)
* should be overridden for this source.
* Flags which dictate source behavior.
*
* \see geometryCheck
* \since QGIS 3.14
*/
bool overrideDefaultGeometryCheck = false;
Flags flags = nullptr;

/**
* Geometry check method to apply to this source. This setting is only
* utilized if QgsProcessingFeatureSourceDefinition::overrideDefaultGeometryCheck is TRUE.
* utilized if the QgsProcessingFeatureSourceDefinition::Flag::FlagCreateIndividualOutputPerInputFeature is
* set in QgsProcessingFeatureSourceDefinition::flags.
*
* \see overrideDefaultGeometryCheck
* \since QGIS 3.14
Expand All @@ -136,7 +150,7 @@ class CORE_EXPORT QgsProcessingFeatureSourceDefinition
return source == other.source
&& selectedFeaturesOnly == other.selectedFeaturesOnly
&& featureLimit == other.featureLimit
&& overrideDefaultGeometryCheck == other.overrideDefaultGeometryCheck
&& flags == other.flags
&& geometryCheck == other.geometryCheck;
}

Expand All @@ -154,6 +168,7 @@ class CORE_EXPORT QgsProcessingFeatureSourceDefinition
};

Q_DECLARE_METATYPE( QgsProcessingFeatureSourceDefinition )
Q_DECLARE_OPERATORS_FOR_FLAGS( QgsProcessingFeatureSourceDefinition::Flags )

/**
* \class QgsProcessingOutputLayerDefinition
Expand Down
2 changes: 1 addition & 1 deletion src/core/processing/qgsprocessingutils.cpp
Expand Up @@ -292,7 +292,7 @@ QgsProcessingFeatureSource *QgsProcessingUtils::variantToSource( const QVariant
selectedFeaturesOnly = fromVar.selectedFeaturesOnly;
featureLimit = fromVar.featureLimit;
val = fromVar.source;
overrideGeometryCheck = fromVar.overrideDefaultGeometryCheck;
overrideGeometryCheck = fromVar.flags & QgsProcessingFeatureSourceDefinition::Flag::FlagOverrideDefaultGeometryCheck;
geometryCheck = fromVar.geometryCheck;
}
else if ( val.canConvert<QgsProcessingOutputLayerDefinition>() )
Expand Down

0 comments on commit 6e2c21e

Please sign in to comment.