From 4eb931e8903792930a01ef3c84bdd66aea0c7ca0 Mon Sep 17 00:00:00 2001 From: David Marteau Date: Mon, 7 Sep 2020 21:21:50 +0200 Subject: [PATCH] Add geometry processing parameter --- .../processing/qgsprocessingparameters.sip.in | 53 ++++++++ python/plugins/processing/core/parameters.py | 5 + python/processing/algfactory.py | 4 + .../models/qgsprocessingmodelalgorithm.cpp | 1 + .../processing/qgsprocessingparameters.cpp | 102 ++++++++++++++ src/core/processing/qgsprocessingparameters.h | 47 +++++++ .../qgsprocessingparametertypeimpl.h | 52 +++++++ src/core/processing/qgsprocessingregistry.cpp | 1 + .../processing/qgsprocessingguiregistry.cpp | 1 + .../qgsprocessingwidgetwrapperimpl.cpp | 111 +++++++++++++++ .../qgsprocessingwidgetwrapperimpl.h | 51 +++++++ tests/src/analysis/testqgsprocessing.cpp | 82 +++++++++++ tests/src/gui/testprocessinggui.cpp | 128 ++++++++++++++++++ 13 files changed, 638 insertions(+) diff --git a/python/core/auto_generated/processing/qgsprocessingparameters.sip.in b/python/core/auto_generated/processing/qgsprocessingparameters.sip.in index 75c17357539..998accc01eb 100644 --- a/python/core/auto_generated/processing/qgsprocessingparameters.sip.in +++ b/python/core/auto_generated/processing/qgsprocessingparameters.sip.in @@ -235,6 +235,8 @@ their acceptable ranges, defaults, etc. sipType = sipType_QgsProcessingParameterExtent; else if ( sipCpp->type() == QgsProcessingParameterPoint::typeName() ) sipType = sipType_QgsProcessingParameterPoint; + else if ( sipCpp->type() == QgsProcessingParameterGeometry::typeName() ) + sipType = sipType_QgsProcessingParameterGeometry; else if ( sipCpp->type() == QgsProcessingParameterFile::typeName() ) sipType = sipType_QgsProcessingParameterFile; else if ( sipCpp->type() == QgsProcessingParameterMatrix::typeName() ) @@ -1170,6 +1172,20 @@ Returns the coordinate reference system associated with an point parameter value .. seealso:: :py:func:`parameterAsPoint` .. versionadded:: 3.8 +%End + + static QgsGeometry parameterAsGeometry( const QgsProcessingParameterDefinition *definition, const QVariantMap ¶meters, QgsProcessingContext &context ); +%Docstring +Evaluates the parameter with matching ``definition`` to a geometry. + +.. versionadded:: 3.16 +%End + + static QgsGeometry parameterAsGeometry( const QgsProcessingParameterDefinition *definition, const QVariant &value, QgsProcessingContext &context ); +%Docstring +Evaluates the parameter with matching ``definition`` and ``value`` to a geometry. + +.. versionadded:: 3.16 %End static QString parameterAsFile( const QgsProcessingParameterDefinition *definition, const QVariantMap ¶meters, QgsProcessingContext &context ); @@ -1527,6 +1543,43 @@ Creates a new parameter using the definition from a script code. }; +class QgsProcessingParameterGeometry : QgsProcessingParameterDefinition +{ +%Docstring +A geometry parameter for processing algorithms. + +.. versionadded:: 3.16 +%End + +%TypeHeaderCode +#include "qgsprocessingparameters.h" +%End + public: + + QgsProcessingParameterGeometry( const QString &name, const QString &description = QString(), const QVariant &defaultValue = QVariant(), bool optional = false ); +%Docstring +Constructor for QgsProcessingParameterGeometry. +%End + + static QString typeName(); +%Docstring +Returns the type name for the parameter class. +%End + virtual QgsProcessingParameterDefinition *clone() const /Factory/; + + virtual QString type() const; + virtual bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = 0 ) const; + + virtual QString valueAsPythonString( const QVariant &value, QgsProcessingContext &context ) const; + + + static QgsProcessingParameterGeometry *fromScriptCode( const QString &name, const QString &description, bool isOptional, const QString &definition ) /Factory/; +%Docstring +Creates a new parameter using the definition from a script code. +%End + +}; + class QgsProcessingParameterFile : QgsProcessingParameterDefinition { %Docstring diff --git a/python/plugins/processing/core/parameters.py b/python/plugins/processing/core/parameters.py index 6e041e72d81..ec2676d2c1d 100755 --- a/python/plugins/processing/core/parameters.py +++ b/python/plugins/processing/core/parameters.py @@ -42,6 +42,7 @@ from qgis.core import (QgsRasterLayer, QgsProcessingParameterCrs, QgsProcessingParameterRange, QgsProcessingParameterPoint, + QgsProcessingParameterGeometry, QgsProcessingParameterEnum, QgsProcessingParameterExtent, QgsProcessingParameterExpression, @@ -74,6 +75,7 @@ PARAMETER_TABLE_FIELD = 'field' PARAMETER_EXTENT = 'extent' PARAMETER_FILE = 'file' PARAMETER_POINT = 'point' +PARAMETER_GEOMETRY = 'geometry' PARAMETER_CRS = 'crs' PARAMETER_MULTIPLE = 'multilayer' PARAMETER_BAND = 'band' @@ -129,6 +131,9 @@ def getParameterFromString(s, context=''): elif clazz == QgsProcessingParameterPoint: if len(params) > 3: params[3] = True if params[3].lower() == 'true' else False + elif clazz == QgsProcessingParameterGeometry: + if len(params) > 3: + params[3] = True if params[3].lower() == 'true' else False elif clazz == QgsProcessingParameterCrs: if len(params) > 3: params[3] = True if params[3].lower() == 'true' else False diff --git a/python/processing/algfactory.py b/python/processing/algfactory.py index 14fda409de2..13048745d94 100644 --- a/python/processing/algfactory.py +++ b/python/processing/algfactory.py @@ -50,6 +50,7 @@ from qgis.core import (QgsProcessingParameterDefinition, QgsProcessingParameterMatrix, QgsProcessingParameterMultipleLayers, QgsProcessingParameterPoint, + QgsProcessingParameterGeometry, QgsProcessingParameterRange, QgsProcessingParameterRasterLayer, QgsProcessingParameterVectorLayer, @@ -326,6 +327,7 @@ class ProcessingAlgFactory(): FIELD = "FIELD", MATRIX = "MATRIX", POINT = "POINT", + GEOMETRY = "GEOMETRY", RANGE = "RANGE", AUTH_CFG = "AUTH_CFG" SCALE = "SCALE" @@ -462,6 +464,7 @@ class ProcessingAlgFactory(): alg.MATRIX: QgsProcessingParameterMatrix alg.MULTILAYER: QgsProcessingParameterMultipleLayers alg.POINT: QgsProcessingParameterPoint + alg.GEOMETRY: QgsProcessingParameterGeometry alg.RANGE: QgsProcessingParameterRange alg.VECTOR_LAYER: QgsProcessingParameterVectorLayer alg.AUTH_CFG: QgsProcessingParameterAuthConfig @@ -519,6 +522,7 @@ input_type_mapping = { ProcessingAlgFactory.MATRIX: QgsProcessingParameterMatrix, ProcessingAlgFactory.MULTILAYER: QgsProcessingParameterMultipleLayers, ProcessingAlgFactory.POINT: QgsProcessingParameterPoint, + ProcessingAlgFactory.GEOMETRY: QgsProcessingParameterGeometry, ProcessingAlgFactory.RANGE: QgsProcessingParameterRange, ProcessingAlgFactory.VECTOR_LAYER: QgsProcessingParameterVectorLayer, ProcessingAlgFactory.AUTH_CFG: QgsProcessingParameterAuthConfig, diff --git a/src/core/processing/models/qgsprocessingmodelalgorithm.cpp b/src/core/processing/models/qgsprocessingmodelalgorithm.cpp index 43a6ce5efdf..a13ec12ec45 100644 --- a/src/core/processing/models/qgsprocessingmodelalgorithm.cpp +++ b/src/core/processing/models/qgsprocessingmodelalgorithm.cpp @@ -851,6 +851,7 @@ QMap QgsProcessingMode << QgsProcessingParameterCrs::typeName() << QgsProcessingParameterRange::typeName() << QgsProcessingParameterPoint::typeName() + << QgsProcessingParameterGeometry::typeName() << QgsProcessingParameterFile::typeName() << QgsProcessingParameterFolderDestination::typeName() << QgsProcessingParameterBand::typeName() diff --git a/src/core/processing/qgsprocessingparameters.cpp b/src/core/processing/qgsprocessingparameters.cpp index ba38e400160..f332010a3a7 100644 --- a/src/core/processing/qgsprocessingparameters.cpp +++ b/src/core/processing/qgsprocessingparameters.cpp @@ -1417,6 +1417,34 @@ QgsCoordinateReferenceSystem QgsProcessingParameters::parameterAsPointCrs( const return QgsCoordinateReferenceSystem(); } +QgsGeometry QgsProcessingParameters::parameterAsGeometry( const QgsProcessingParameterDefinition *definition, const QVariantMap ¶meters, QgsProcessingContext &context ) +{ + if ( !definition ) + return QgsGeometry(); + + return parameterAsGeometry( definition, parameters.value( definition->name() ), context ); +} + +QgsGeometry QgsProcessingParameters::parameterAsGeometry( const QgsProcessingParameterDefinition *definition, const QVariant &value, QgsProcessingContext &context ) +{ + if ( !definition ) + return QgsGeometry(); + + QVariant val = value; + if ( val.canConvert< QgsGeometry >() ) + { + return val.value(); + } + + QString valueAsString = parameterAsString( definition, value, context ); + if ( !valueAsString.isEmpty() ) + { + return QgsGeometry::fromWkt( valueAsString ); + } + + return QgsGeometry(); +} + QString QgsProcessingParameters::parameterAsFile( const QgsProcessingParameterDefinition *definition, const QVariantMap ¶meters, QgsProcessingContext &context ) { if ( !definition ) @@ -2002,6 +2030,8 @@ QgsProcessingParameterDefinition *QgsProcessingParameters::parameterFromScriptCo return QgsProcessingParameterExtent::fromScriptCode( name, description, isOptional, definition ); else if ( type == QStringLiteral( "point" ) ) return QgsProcessingParameterPoint::fromScriptCode( name, description, isOptional, definition ); + else if ( type == QStringLiteral( "geometry" ) ) + return QgsProcessingParameterGeometry::fromScriptCode( name, description, isOptional, definition ); else if ( type == QStringLiteral( "file" ) ) return QgsProcessingParameterFile::fromScriptCode( name, description, isOptional, definition, QgsProcessingParameterFile::File ); else if ( type == QStringLiteral( "folder" ) ) @@ -2770,6 +2800,78 @@ QgsProcessingParameterPoint *QgsProcessingParameterPoint::fromScriptCode( const return new QgsProcessingParameterPoint( name, description, definition, isOptional ); } +QgsProcessingParameterGeometry::QgsProcessingParameterGeometry( const QString &name, const QString &description, + const QVariant &defaultValue, bool optional ) + : QgsProcessingParameterDefinition( name, description, defaultValue, optional ) +{ + +} + +QgsProcessingParameterDefinition *QgsProcessingParameterGeometry::clone() const +{ + return new QgsProcessingParameterGeometry( *this ); +} + +bool QgsProcessingParameterGeometry::checkValueIsAcceptable( const QVariant &input, QgsProcessingContext * ) const +{ + if ( !input.isValid() ) + return mFlags & FlagOptional; + + if ( input.canConvert() ) + { + return true; + } + + if ( input.canConvert< QgsGeometry >() ) + { + return true; + } + + if ( input.type() == QVariant::String ) + { + if ( input.toString().isEmpty() ) + return mFlags & FlagOptional; + } + + QgsGeometry g = QgsGeometry::fromWkt( input.toString() ); + if ( ! g.isNull() ) + { + return true; + } + else + { + QgsMessageLog::logMessage( QObject::tr( "Error creating geometry: \"%1\"" ).arg( g.lastError() ), QObject::tr( "Processing" ) ); + return false; + } + +} + +QString QgsProcessingParameterGeometry::valueAsPythonString( const QVariant &value, QgsProcessingContext &context ) const +{ + if ( !value.isValid() ) + return QStringLiteral( "None" ); + + if ( value.canConvert() ) + return QStringLiteral( "QgsProperty.fromExpression('%1')" ).arg( value.value< QgsProperty >().asExpression() ); + + if ( value.canConvert< QgsGeometry >() ) + { + const QgsGeometry g = value.value(); + if ( !g.isNull() ) + { + const QString wkt = g.asWkt(); + return QStringLiteral( "QgsGeometry.fromWkt('%1')" ).arg( wkt ); + } + } + + return QgsProcessingParameterDefinition::valueAsPythonString( value, context ); +} + +QgsProcessingParameterGeometry *QgsProcessingParameterGeometry::fromScriptCode( const QString &name, const QString &description, bool isOptional, const QString &definition ) +{ + return new QgsProcessingParameterGeometry( name, description, definition, isOptional ); +} + QgsProcessingParameterFile::QgsProcessingParameterFile( const QString &name, const QString &description, Behavior behavior, const QString &extension, const QVariant &defaultValue, bool optional, const QString &fileFilter ) : QgsProcessingParameterDefinition( name, description, defaultValue, optional ) , mBehavior( behavior ) diff --git a/src/core/processing/qgsprocessingparameters.h b/src/core/processing/qgsprocessingparameters.h index feee55c0f48..a5169c39145 100644 --- a/src/core/processing/qgsprocessingparameters.h +++ b/src/core/processing/qgsprocessingparameters.h @@ -342,6 +342,8 @@ class CORE_EXPORT QgsProcessingParameterDefinition sipType = sipType_QgsProcessingParameterExtent; else if ( sipCpp->type() == QgsProcessingParameterPoint::typeName() ) sipType = sipType_QgsProcessingParameterPoint; + else if ( sipCpp->type() == QgsProcessingParameterGeometry::typeName() ) + sipType = sipType_QgsProcessingParameterGeometry; else if ( sipCpp->type() == QgsProcessingParameterFile::typeName() ) sipType = sipType_QgsProcessingParameterFile; else if ( sipCpp->type() == QgsProcessingParameterMatrix::typeName() ) @@ -1247,6 +1249,20 @@ class CORE_EXPORT QgsProcessingParameters */ static QgsCoordinateReferenceSystem parameterAsPointCrs( const QgsProcessingParameterDefinition *definition, const QVariant &value, QgsProcessingContext &context ); + /** + * Evaluates the parameter with matching \a definition to a geometry. + * + * \since QGIS 3.16 + */ + static QgsGeometry parameterAsGeometry( const QgsProcessingParameterDefinition *definition, const QVariantMap ¶meters, QgsProcessingContext &context ); + + /** + * Evaluates the parameter with matching \a definition and \a value to a geometry. + * + * \since QGIS 3.16 + */ + static QgsGeometry parameterAsGeometry( const QgsProcessingParameterDefinition *definition, const QVariant &value, QgsProcessingContext &context ); + /** * Evaluates the parameter with matching \a definition to a file/folder name. */ @@ -1568,6 +1584,37 @@ class CORE_EXPORT QgsProcessingParameterPoint : public QgsProcessingParameterDef }; +/** + * \class QgsProcessingParameterGeometry + * \ingroup core + * A geometry parameter for processing algorithms. + * \since QGIS 3.16 + */ +class CORE_EXPORT QgsProcessingParameterGeometry : public QgsProcessingParameterDefinition +{ + public: + + /** + * Constructor for QgsProcessingParameterGeometry. + */ + QgsProcessingParameterGeometry( const QString &name, const QString &description = QString(), const QVariant &defaultValue = QVariant(), bool optional = false ); + + /** + * Returns the type name for the parameter class. + */ + static QString typeName() { return QStringLiteral( "geometry" ); } + QgsProcessingParameterDefinition *clone() const override SIP_FACTORY; + QString type() const override { return typeName(); } + bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = nullptr ) const override; + QString valueAsPythonString( const QVariant &value, QgsProcessingContext &context ) const override; + + /** + * Creates a new parameter using the definition from a script code. + */ + static QgsProcessingParameterGeometry *fromScriptCode( const QString &name, const QString &description, bool isOptional, const QString &definition ) SIP_FACTORY; + +}; + /** * \class QgsProcessingParameterFile * \ingroup core diff --git a/src/core/processing/qgsprocessingparametertypeimpl.h b/src/core/processing/qgsprocessingparametertypeimpl.h index 548443cc885..f053887a4fb 100644 --- a/src/core/processing/qgsprocessingparametertypeimpl.h +++ b/src/core/processing/qgsprocessingparametertypeimpl.h @@ -518,6 +518,58 @@ class CORE_EXPORT QgsProcessingParameterTypePoint : public QgsProcessingParamete } }; +/** + * A geometry parameter for processing algorithms. + * + * \ingroup core + * \note No Python bindings available. Get your copy from QgsApplication.processingRegistry().parameterType('geometry') + * \since QGIS 3.16 + */ +class CORE_EXPORT QgsProcessingParameterTypeGeometry : public QgsProcessingParameterType +{ + QgsProcessingParameterDefinition *create( const QString &name ) const override SIP_FACTORY + { + return new QgsProcessingParameterGeometry( name ); + } + + QString description() const override + { + return QCoreApplication::translate( "Processing", "A geometry parameter." ); + } + + QString name() const override + { + return QCoreApplication::translate( "Processing", "Geometry" ); + } + + QString id() const override + { + return QStringLiteral( "geometry" ); + } + + QString pythonImportString() const override + { + return QStringLiteral( "from qgis.core import QgsProcessingParameterGeometry" ); + } + + QString className() const override + { + return QStringLiteral( "QgsProcessingParameterGeometry" ); + } + + QStringList acceptedPythonTypes() const override + { + return QStringList() << QObject::tr( "str: as Well-Known Text string (WKT)" ) + << QStringLiteral( "QgsGeometry" ) + << QStringLiteral( "QgsProperty" ); + } + + QStringList acceptedStringValues() const override + { + return QStringList() << QObject::tr( "Well-Known Text string (WKT)" ); + } +}; + /** * An enum based parameter for processing algorithms, allowing for selection from predefined values. * diff --git a/src/core/processing/qgsprocessingregistry.cpp b/src/core/processing/qgsprocessingregistry.cpp index 236d06dbe07..47457a27c06 100644 --- a/src/core/processing/qgsprocessingregistry.cpp +++ b/src/core/processing/qgsprocessingregistry.cpp @@ -35,6 +35,7 @@ QgsProcessingRegistry::QgsProcessingRegistry( QObject *parent SIP_TRANSFERTHIS ) addParameterType( new QgsProcessingParameterTypeCrs() ); addParameterType( new QgsProcessingParameterTypeRange() ); addParameterType( new QgsProcessingParameterTypePoint() ); + addParameterType( new QgsProcessingParameterTypeGeometry() ); addParameterType( new QgsProcessingParameterTypeEnum() ); addParameterType( new QgsProcessingParameterTypeExtent() ); addParameterType( new QgsProcessingParameterTypeMatrix() ); diff --git a/src/gui/processing/qgsprocessingguiregistry.cpp b/src/gui/processing/qgsprocessingguiregistry.cpp index f5b6a0b0a56..f546778cb30 100644 --- a/src/gui/processing/qgsprocessingguiregistry.cpp +++ b/src/gui/processing/qgsprocessingguiregistry.cpp @@ -47,6 +47,7 @@ QgsProcessingGuiRegistry::QgsProcessingGuiRegistry() addParameterWidgetFactory( new QgsProcessingLayoutWidgetWrapper() ); addParameterWidgetFactory( new QgsProcessingLayoutItemWidgetWrapper() ); addParameterWidgetFactory( new QgsProcessingPointWidgetWrapper() ); + addParameterWidgetFactory( new QgsProcessingGeometryWidgetWrapper() ); addParameterWidgetFactory( new QgsProcessingColorWidgetWrapper() ); addParameterWidgetFactory( new QgsProcessingCoordinateOperationWidgetWrapper() ); addParameterWidgetFactory( new QgsProcessingFieldWidgetWrapper() ); diff --git a/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp b/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp index 8615c3f54f3..ce8a5cb745e 100644 --- a/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp +++ b/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp @@ -3161,6 +3161,117 @@ QgsProcessingAbstractParameterDefinitionWidget *QgsProcessingPointWidgetWrapper: } +// +// QgsProcessingGeometryWidgetWrapper +// + + +QgsProcessingGeometryParameterDefinitionWidget::QgsProcessingGeometryParameterDefinitionWidget( QgsProcessingContext &context, const QgsProcessingParameterWidgetContext &widgetContext, const QgsProcessingParameterDefinition *definition, const QgsProcessingAlgorithm *algorithm, QWidget *parent ) + : QgsProcessingAbstractParameterDefinitionWidget( context, widgetContext, definition, algorithm, parent ) +{ + QVBoxLayout *vlayout = new QVBoxLayout(); + vlayout->setMargin( 0 ); + vlayout->setContentsMargins( 0, 0, 0, 0 ); + + vlayout->addWidget( new QLabel( tr( "Default value" ) ) ); + + mDefaultLineEdit = new QLineEdit(); + mDefaultLineEdit->setToolTip( tr( "Geometry as WKT" ) ); + mDefaultLineEdit->setPlaceholderText( tr( "Geometry as WKT" ) ); + if ( const QgsProcessingParameterGeometry *geometryParam = dynamic_cast( definition ) ) + { + QgsGeometry g = QgsProcessingParameters::parameterAsGeometry( geometryParam, geometryParam->defaultValue(), context ); + if ( !g.isNull() ) + mDefaultLineEdit->setText( g.asWkt() ); + } + + vlayout->addWidget( mDefaultLineEdit ); + setLayout( vlayout ); +} + +QgsProcessingParameterDefinition *QgsProcessingGeometryParameterDefinitionWidget::createParameter( const QString &name, const QString &description, QgsProcessingParameterDefinition::Flags flags ) const +{ + auto param = qgis::make_unique< QgsProcessingParameterGeometry >( name, description, mDefaultLineEdit->text() ); + param->setFlags( flags ); + return param.release(); +} + +QgsProcessingGeometryWidgetWrapper::QgsProcessingGeometryWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type, QWidget *parent ) + : QgsAbstractProcessingParameterWidgetWrapper( parameter, type, parent ) +{ + +} + +QWidget *QgsProcessingGeometryWidgetWrapper::createWidget() +{ + switch ( type() ) + { + case QgsProcessingGui::Standard: + case QgsProcessingGui::Modeler: + case QgsProcessingGui::Batch: + { + mLineEdit = new QLineEdit(); + mLineEdit->setToolTip( parameterDefinition()->toolTip() ); + connect( mLineEdit, &QLineEdit::textChanged, this, [ = ] + { + emit widgetValueHasChanged( this ); + } ); + return mLineEdit; + } + } + return nullptr; +} + +void QgsProcessingGeometryWidgetWrapper::setWidgetValue( const QVariant &value, QgsProcessingContext &context ) +{ + if ( mLineEdit ) + { + QString v = QgsProcessingParameters::parameterAsString( parameterDefinition(), value, context ); + mLineEdit->setText( v ); + } +} + +QVariant QgsProcessingGeometryWidgetWrapper::widgetValue() const +{ + if ( mLineEdit ) + return mLineEdit->text().isEmpty() ? QVariant() : mLineEdit->text(); + else + return QVariant(); +} + +QStringList QgsProcessingGeometryWidgetWrapper::compatibleParameterTypes() const +{ + return QStringList() + << QgsProcessingParameterGeometry::typeName() + << QgsProcessingParameterString::typeName(); +} + +QStringList QgsProcessingGeometryWidgetWrapper::compatibleOutputTypes() const +{ + return QStringList() + << QgsProcessingOutputString::typeName(); +} + +QString QgsProcessingGeometryWidgetWrapper::modelerExpressionFormatString() const +{ + return tr( "string in the Well-Known-Text format or a geometry value" ); +} + +QString QgsProcessingGeometryWidgetWrapper::parameterType() const +{ + return QgsProcessingParameterGeometry::typeName(); +} + +QgsAbstractProcessingParameterWidgetWrapper *QgsProcessingGeometryWidgetWrapper::createWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type ) +{ + return new QgsProcessingGeometryWidgetWrapper( parameter, type ); +} + +QgsProcessingAbstractParameterDefinitionWidget *QgsProcessingGeometryWidgetWrapper::createParameterDefinitionWidget( QgsProcessingContext &context, const QgsProcessingParameterWidgetContext &widgetContext, const QgsProcessingParameterDefinition *definition, const QgsProcessingAlgorithm *algorithm ) +{ + return new QgsProcessingGeometryParameterDefinitionWidget( context, widgetContext, definition, algorithm ); +} + // // QgsProcessingColorWidgetWrapper diff --git a/src/gui/processing/qgsprocessingwidgetwrapperimpl.h b/src/gui/processing/qgsprocessingwidgetwrapperimpl.h index 6b9e5180fcb..6283fb17343 100644 --- a/src/gui/processing/qgsprocessingwidgetwrapperimpl.h +++ b/src/gui/processing/qgsprocessingwidgetwrapperimpl.h @@ -1022,8 +1022,59 @@ class GUI_EXPORT QgsProcessingPointWidgetWrapper : public QgsAbstractProcessingP friend class TestProcessingGui; }; +class GUI_EXPORT QgsProcessingGeometryParameterDefinitionWidget : public QgsProcessingAbstractParameterDefinitionWidget +{ + Q_OBJECT + public: + QgsProcessingGeometryParameterDefinitionWidget( QgsProcessingContext &context, + const QgsProcessingParameterWidgetContext &widgetContext, + const QgsProcessingParameterDefinition *definition = nullptr, + const QgsProcessingAlgorithm *algorithm = nullptr, QWidget *parent SIP_TRANSFERTHIS = nullptr ); + QgsProcessingParameterDefinition *createParameter( const QString &name, const QString &description, QgsProcessingParameterDefinition::Flags flags ) const override; + private: + + QLineEdit *mDefaultLineEdit = nullptr; + +}; + +class GUI_EXPORT QgsProcessingGeometryWidgetWrapper : public QgsAbstractProcessingParameterWidgetWrapper, public QgsProcessingParameterWidgetFactoryInterface +{ + Q_OBJECT + + public: + + QgsProcessingGeometryWidgetWrapper( const QgsProcessingParameterDefinition *parameter = nullptr, + QgsProcessingGui::WidgetType type = QgsProcessingGui::Standard, QWidget *parent = nullptr ); + + // QgsProcessingParameterWidgetFactoryInterface + QString parameterType() const override; + QgsAbstractProcessingParameterWidgetWrapper *createWidgetWrapper( const QgsProcessingParameterDefinition *parameter, QgsProcessingGui::WidgetType type ) override; + QgsProcessingAbstractParameterDefinitionWidget *createParameterDefinitionWidget( + QgsProcessingContext &context, + const QgsProcessingParameterWidgetContext &widgetContext, + const QgsProcessingParameterDefinition *definition = nullptr, + const QgsProcessingAlgorithm *algorithm = nullptr ) override; + + // QgsProcessingParameterWidgetWrapper interface + QWidget *createWidget() override SIP_FACTORY; + + protected: + + void setWidgetValue( const QVariant &value, QgsProcessingContext &context ) override; + QVariant widgetValue() const override; + + QStringList compatibleParameterTypes() const override; + + QStringList compatibleOutputTypes() const override; + QString modelerExpressionFormatString() const override; + private: + + QLineEdit *mLineEdit = nullptr; + + friend class TestProcessingGui; +}; class GUI_EXPORT QgsProcessingExtentParameterDefinitionWidget : public QgsProcessingAbstractParameterDefinitionWidget { diff --git a/tests/src/analysis/testqgsprocessing.cpp b/tests/src/analysis/testqgsprocessing.cpp index cfcf1080541..ca9f3f78291 100644 --- a/tests/src/analysis/testqgsprocessing.cpp +++ b/tests/src/analysis/testqgsprocessing.cpp @@ -568,6 +568,7 @@ class TestQgsProcessing: public QObject void parameterMapLayer(); void parameterExtent(); void parameterPoint(); + void parameterGeometry(); void parameterFile(); void parameterMatrix(); void parameterLayerList(); @@ -3379,6 +3380,87 @@ void TestQgsProcessing::parameterPoint() QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); } +void TestQgsProcessing::parameterGeometry() +{ + QgsProcessingContext context; + + // not optional! + std::unique_ptr< QgsProcessingParameterGeometry > def( new QgsProcessingParameterGeometry( "non_optional", QString(), QString( "Point(1 2)" ), false ) ); + QVERIFY( !def->checkValueIsAcceptable( false ) ); + QVERIFY( !def->checkValueIsAcceptable( true ) ); + QVERIFY( !def->checkValueIsAcceptable( 5 ) ); + QVERIFY( !def->checkValueIsAcceptable( "Nonsense string" ) ); + QVERIFY( !def->checkValueIsAcceptable( "" ) ); + QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); + QVERIFY( !def->checkValueIsAcceptable( QString( "LineString(10 10, 20 a)" ) ) ); + QVERIFY( def->checkValueIsAcceptable( QString( "LineString(10 10, 20 20)" ) ) ); + QVERIFY( def->checkValueIsAcceptable( QgsGeometry::fromPointXY( QgsPointXY( 1, 2 ) ) ) ); + QVERIFY( def->checkValueIsAcceptable( QgsGeometry::fromWkt( QStringLiteral( "LineString(10 10, 20 20)" ) ) ) ); + + // string representing a geometry + QVariantMap params; + params.insert( "non_optional", QString( "LineString(10 10, 20 20)" ) ); + QVERIFY( def->checkValueIsAcceptable( "LineString(10 10, 20 20)" ) ); + QgsGeometry geometry = QgsProcessingParameters::parameterAsGeometry( def.get(), params, context ); + QCOMPARE( geometry.asWkt(), QStringLiteral( "LineString (10 10, 20 20)" ) ); + + // nonsense string + params.insert( "non_optional", QString( "i'm not a geometry, and nothing you can do will make me one" ) ); + geometry = QgsProcessingParameters::parameterAsGeometry( def.get(), params, context ); + QVERIFY( geometry.isNull() ); + + QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) ); + QCOMPARE( def->valueAsPythonString( "LineString( 10 10, 20 20)", context ), QStringLiteral( "'LineString( 10 10, 20 20)'" ) ); + QCOMPARE( def->valueAsPythonString( QgsGeometry::fromWkt( QStringLiteral( "LineString( 10 10, 20 20)" ) ), context ), QStringLiteral( "QgsGeometry.fromWkt('LineString (10 10, 20 20)')" ) ); + + QString pythonCode = def->asPythonString(); + QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterGeometry('non_optional', '', defaultValue='Point(1 2)')" ) ); + + QString code = def->asScriptCode(); + QCOMPARE( code, QStringLiteral( "##non_optional=geometry Point(1 2)" ) ); + std::unique_ptr< QgsProcessingParameterGeometry > fromCode( dynamic_cast< QgsProcessingParameterGeometry * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); + QVERIFY( fromCode.get() ); + QCOMPARE( fromCode->name(), def->name() ); + QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); + QCOMPARE( fromCode->flags(), def->flags() ); + QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); + + QVariantMap map = def->toVariantMap(); + QgsProcessingParameterGeometry fromMap( "x" ); + QVERIFY( fromMap.fromVariantMap( map ) ); + QCOMPARE( fromMap.name(), def->name() ); + QCOMPARE( fromMap.description(), def->description() ); + QCOMPARE( fromMap.flags(), def->flags() ); + QCOMPARE( fromMap.defaultValue(), def->defaultValue() ); + def.reset( dynamic_cast< QgsProcessingParameterGeometry *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) ); + QVERIFY( dynamic_cast< QgsProcessingParameterGeometry *>( def.get() ) ); + + // optional + def.reset( new QgsProcessingParameterGeometry( "optional", QString(), QString( "Point(-1 3)" ), true ) ); + QVERIFY( def->checkValueIsAcceptable( "LineString(10 10, 20 20)" ) ); + QVERIFY( !def->checkValueIsAcceptable( "Point(-1 a)" ) ); + QVERIFY( def->checkValueIsAcceptable( "" ) ); + QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); + + params.insert( "optional", QVariant() ); + geometry = QgsProcessingParameters::parameterAsGeometry( def.get(), params, context ); + QCOMPARE( geometry.asWkt(), QStringLiteral( "Point (-1 3)" ) ); + + pythonCode = def->asPythonString(); + QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterGeometry('optional', '', optional=True, defaultValue='Point(-1 3)')" ) ); + + code = def->asScriptCode(); + QCOMPARE( code, QStringLiteral( "##optional=optional geometry Point(-1 3)" ) ); + fromCode.reset( dynamic_cast< QgsProcessingParameterGeometry * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) ); + QVERIFY( fromCode.get() ); + QCOMPARE( fromCode->name(), def->name() ); + QCOMPARE( fromCode->description(), QStringLiteral( "optional" ) ); + QCOMPARE( fromCode->flags(), def->flags() ); + QCOMPARE( fromCode->defaultValue(), def->defaultValue() ); +} + + + void TestQgsProcessing::parameterFile() { QgsProcessingContext context; diff --git a/tests/src/gui/testprocessinggui.cpp b/tests/src/gui/testprocessinggui.cpp index b16fdc75e45..4c42c606363 100644 --- a/tests/src/gui/testprocessinggui.cpp +++ b/tests/src/gui/testprocessinggui.cpp @@ -219,6 +219,7 @@ class TestProcessingGui : public QObject void testLayoutItemWrapper(); void testPointPanel(); void testPointWrapper(); + void testGeometryWrapper(); void testExtentWrapper(); void testColorWrapper(); void testCoordinateOperationWrapper(); @@ -4510,6 +4511,7 @@ void TestProcessingGui::testPointPanel() panel.reset(); } + void TestProcessingGui::testPointWrapper() { auto testWrapper = []( QgsProcessingGui::WidgetType type ) @@ -4688,6 +4690,132 @@ void TestProcessingGui::testPointWrapper() } + +void TestProcessingGui::testGeometryWrapper() +{ + auto testWrapper = []( QgsProcessingGui::WidgetType type ) + { + // non optional + QgsProcessingParameterGeometry param( QStringLiteral( "geometry" ), QStringLiteral( "geometry" ), false ); + + QgsProcessingGeometryWidgetWrapper wrapper( ¶m, type ); + + QgsProcessingContext context; + QWidget *w = wrapper.createWrappedWidget( context ); + + QSignalSpy spy( &wrapper, &QgsProcessingLayoutItemWidgetWrapper::widgetValueHasChanged ); + wrapper.setWidgetValue( QStringLiteral( "POINT (1 2)" ), context ); + QCOMPARE( spy.count(), 1 ); + QCOMPARE( wrapper.widgetValue().toString().toLower(), QStringLiteral( "point (1 2)" ) ); + QCOMPARE( static_cast< QLineEdit * >( wrapper.wrappedWidget() )->text().toLower(), QStringLiteral( "point (1 2)" ).toLower() ); + wrapper.setWidgetValue( QString(), context ); + QCOMPARE( spy.count(), 2 ); + QVERIFY( wrapper.widgetValue().toString().isEmpty() ); + QVERIFY( static_cast< QLineEdit * >( wrapper.wrappedWidget() )->text().isEmpty() ); + + QLabel *l = wrapper.createWrappedLabel(); + if ( wrapper.type() != QgsProcessingGui::Batch ) + { + QVERIFY( l ); + QCOMPARE( l->text(), QStringLiteral( "geometry" ) ); + QCOMPARE( l->toolTip(), param.toolTip() ); + delete l; + } + else + { + QVERIFY( !l ); + } + + // check signal + static_cast< QLineEdit * >( wrapper.wrappedWidget() )->setText( QStringLiteral( "b" ) ); + QCOMPARE( spy.count(), 3 ); + static_cast< QLineEdit * >( wrapper.wrappedWidget() )->clear(); + QCOMPARE( spy.count(), 4 ); + + delete w; + + // optional + + QgsProcessingParameterGeometry param2( QStringLiteral( "geometry" ), QStringLiteral( "geometry" ), QVariant(), true ); + + QgsProcessingGeometryWidgetWrapper wrapper2( ¶m2, type ); + + w = wrapper2.createWrappedWidget( context ); + + QSignalSpy spy2( &wrapper2, &QgsProcessingLayoutItemWidgetWrapper::widgetValueHasChanged ); + wrapper2.setWidgetValue( "POINT (1 2)", context ); + QCOMPARE( spy2.count(), 1 ); + QCOMPARE( wrapper2.widgetValue().toString().toLower(), QStringLiteral( "point (1 2)" ) ); + QCOMPARE( static_cast< QLineEdit * >( wrapper2.wrappedWidget() )->text().toLower(), QStringLiteral( "point (1 2)" ) ); + + wrapper2.setWidgetValue( QVariant(), context ); + QCOMPARE( spy2.count(), 2 ); + QVERIFY( !wrapper2.widgetValue().isValid() ); + QVERIFY( static_cast< QLineEdit * >( wrapper2.wrappedWidget() )->text().isEmpty() ); + + wrapper2.setWidgetValue( "POINT (1 3)", context ); + QCOMPARE( spy2.count(), 3 ); + wrapper2.setWidgetValue( "", context ); + QCOMPARE( spy2.count(), 4 ); + QVERIFY( !wrapper2.widgetValue().isValid() ); + QVERIFY( static_cast< QLineEdit * >( wrapper2.wrappedWidget() )->text().isEmpty() ); + + + // check signals + wrapper2.setWidgetValue( "1,3", context ); + QCOMPARE( spy2.count(), 5 ); + static_cast< QLineEdit * >( wrapper2.wrappedWidget() )->clear(); + QCOMPARE( spy2.count(), 6 ); + + delete w; + }; + + // standard wrapper + testWrapper( QgsProcessingGui::Standard ); + + + // batch wrapper + testWrapper( QgsProcessingGui::Batch ); + + // modeler wrapper + testWrapper( QgsProcessingGui::Modeler ); + + + // config widget + QgsProcessingContext context; + QgsProcessingParameterWidgetContext widgetContext; + std::unique_ptr< QgsProcessingParameterDefinitionWidget > widget = qgis::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "geometry" ), context, widgetContext ); + std::unique_ptr< QgsProcessingParameterDefinition > def( widget->createParameter( QStringLiteral( "param_name" ) ) ); + QCOMPARE( def->name(), QStringLiteral( "param_name" ) ); + QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); // should default to mandatory + QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) ); + + // using a parameter definition as initial values + QgsProcessingParameterGeometry geometryParam( QStringLiteral( "n" ), QStringLiteral( "test desc" ), QStringLiteral( "POINT (1 2)" ) ); + + widget = qgis::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "geometry" ), context, widgetContext, &geometryParam ); + + def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) ); + QCOMPARE( def->name(), QStringLiteral( "param_name" ) ); + QCOMPARE( def->description(), QStringLiteral( "test desc" ) ); + QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagOptional ) ); + QVERIFY( !( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ) ); + QCOMPARE( static_cast< QgsProcessingParameterGeometry * >( def.get() )->defaultValue().toString().toLower(), QStringLiteral( "point (1 2)" ) ); + geometryParam.setFlags( QgsProcessingParameterDefinition::FlagAdvanced | QgsProcessingParameterDefinition::FlagOptional ); + geometryParam.setDefaultValue( QStringLiteral( "POINT (4 7)" ) ); + widget = qgis::make_unique< QgsProcessingParameterDefinitionWidget >( QStringLiteral( "geometry" ), context, widgetContext, &geometryParam ); + def.reset( widget->createParameter( QStringLiteral( "param_name" ) ) ); + QCOMPARE( def->name(), QStringLiteral( "param_name" ) ); + QCOMPARE( def->description(), QStringLiteral( "test desc" ) ); + QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagOptional ); + QVERIFY( def->flags() & QgsProcessingParameterDefinition::FlagAdvanced ); + QCOMPARE( static_cast< QgsProcessingParameterGeometry * >( def.get() )->defaultValue().toString().toLower(), QStringLiteral( "point (4 7)" ) ); + +} + + + + void TestProcessingGui::testExtentWrapper() { auto testWrapper = []( QgsProcessingGui::WidgetType type )