diff --git a/python/core/auto_generated/processing/qgsprocessingparameters.sip.in b/python/core/auto_generated/processing/qgsprocessingparameters.sip.in index 314077e1735..66a49e5494f 100644 --- a/python/core/auto_generated/processing/qgsprocessingparameters.sip.in +++ b/python/core/auto_generated/processing/qgsprocessingparameters.sip.in @@ -183,6 +183,8 @@ their acceptable ranges, defaults, etc. sipType = sipType_QgsProcessingParameterFolderDestination; else if ( sipCpp->type() == QgsProcessingParameterBand::typeName() ) sipType = sipType_QgsProcessingParameterBand; + else if ( sipCpp->type() == QgsProcessingParameterLayout::typeName() ) + sipType = sipType_QgsProcessingParameterLayout; else sipType = nullptr; %End @@ -2747,6 +2749,49 @@ Sets whether multiple band selections are permitted. }; +class QgsProcessingParameterLayout : QgsProcessingParameterDefinition +{ +%Docstring +A print layout parameter, allowing users to select a print layout. + +QgsProcessingParameterLayout should be evaluated by calling :py:func:`QgsProcessingAlgorithm.parameterAsString()` +This will return the name of the target print layout. + +.. versionadded:: 3.8 +%End + +%TypeHeaderCode +#include "qgsprocessingparameters.h" +%End + public: + + QgsProcessingParameterLayout( const QString &name, const QString &description = QString(), const QVariant &defaultValue = QVariant(), + bool optional = false ); +%Docstring +Constructor for QgsProcessingParameterLayout. +%End + + static QString typeName(); +%Docstring +Returns the type name for the parameter class. +%End + virtual QgsProcessingParameterDefinition *clone() const /Factory/; + + virtual QString type() const; + virtual QString valueAsPythonString( const QVariant &value, QgsProcessingContext &context ) const; + + virtual QString asScriptCode() const; + + virtual QString asPythonString( QgsProcessing::PythonOutputType outputType = QgsProcessing::PythonQgsProcessingAlgorithmSubclass ) const; + + + static QgsProcessingParameterLayout *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 + +}; + diff --git a/src/core/processing/qgsprocessingparameters.cpp b/src/core/processing/qgsprocessingparameters.cpp index 8f7e17ed871..1cd751759b0 100644 --- a/src/core/processing/qgsprocessingparameters.cpp +++ b/src/core/processing/qgsprocessingparameters.cpp @@ -1519,6 +1519,8 @@ QgsProcessingParameterDefinition *QgsProcessingParameters::parameterFromVariantM def.reset( new QgsProcessingParameterBand( name ) ); else if ( type == QgsProcessingParameterMeshLayer::typeName() ) def.reset( new QgsProcessingParameterMeshLayer( name ) ); + else if ( type == QgsProcessingParameterLayout::typeName() ) + def.reset( new QgsProcessingParameterLayout( name ) ); else { QgsProcessingParameterType *paramType = QgsApplication::instance()->processingRegistry()->parameterType( type ); @@ -1603,6 +1605,8 @@ QgsProcessingParameterDefinition *QgsProcessingParameters::parameterFromScriptCo return QgsProcessingParameterBand::fromScriptCode( name, description, isOptional, definition ); else if ( type == QStringLiteral( "mesh" ) ) return QgsProcessingParameterMeshLayer::fromScriptCode( name, description, isOptional, definition ); + else if ( type == QStringLiteral( "layout" ) ) + return QgsProcessingParameterLayout::fromScriptCode( name, description, isOptional, definition ); return nullptr; } @@ -5198,3 +5202,73 @@ bool QgsProcessingParameterDistance::fromVariantMap( const QVariantMap &map ) mDefaultUnit = static_cast< QgsUnitTypes::DistanceUnit>( map.value( QStringLiteral( "default_unit" ), QgsUnitTypes::DistanceUnknownUnit ).toInt() ); return true; } + + +// +// QgsProcessingParameterLayout +// + +QgsProcessingParameterLayout::QgsProcessingParameterLayout( const QString &name, const QString &description, const QVariant &defaultValue, bool optional ) + : QgsProcessingParameterDefinition( name, description, defaultValue, optional ) +{} + +QgsProcessingParameterDefinition *QgsProcessingParameterLayout::clone() const +{ + return new QgsProcessingParameterLayout( *this ); +} + +QString QgsProcessingParameterLayout::valueAsPythonString( const QVariant &value, QgsProcessingContext & ) const +{ + if ( !value.isValid() || value.isNull() ) + return QStringLiteral( "None" ); + + if ( value.canConvert() ) + return QStringLiteral( "QgsProperty.fromExpression('%1')" ).arg( value.value< QgsProperty >().asExpression() ); + + QString s = value.toString(); + return QgsProcessingUtils::stringToPythonLiteral( s ); +} + +QString QgsProcessingParameterLayout::asScriptCode() const +{ + QString code = QStringLiteral( "##%1=" ).arg( mName ); + if ( mFlags & FlagOptional ) + code += QStringLiteral( "optional " ); + code += QStringLiteral( "layout " ); + + code += mDefault.toString(); + return code.trimmed(); +} + +QString QgsProcessingParameterLayout::asPythonString( const QgsProcessing::PythonOutputType outputType ) const +{ + switch ( outputType ) + { + case QgsProcessing::PythonQgsProcessingAlgorithmSubclass: + { + QString code = QStringLiteral( "QgsProcessingParameterLayout('%1', '%2'" ).arg( name(), description() ); + if ( mFlags & FlagOptional ) + code += QStringLiteral( ", optional=True" ); + QgsProcessingContext c; + code += QStringLiteral( ", defaultValue=%1)" ).arg( valueAsPythonString( mDefault, c ) ); + return code; + } + } + return QString(); +} + +QgsProcessingParameterLayout *QgsProcessingParameterLayout::fromScriptCode( const QString &name, const QString &description, bool isOptional, const QString &definition ) +{ + QString def = definition; + + if ( def.startsWith( '"' ) || def.startsWith( '\'' ) ) + def = def.mid( 1 ); + if ( def.endsWith( '"' ) || def.endsWith( '\'' ) ) + def.chop( 1 ); + + QVariant defaultValue = def; + if ( def == QStringLiteral( "None" ) ) + defaultValue = QVariant(); + + return new QgsProcessingParameterLayout( name, description, defaultValue, isOptional ); +} diff --git a/src/core/processing/qgsprocessingparameters.h b/src/core/processing/qgsprocessingparameters.h index f06b78d6890..018d9aa658f 100644 --- a/src/core/processing/qgsprocessingparameters.h +++ b/src/core/processing/qgsprocessingparameters.h @@ -258,6 +258,8 @@ class CORE_EXPORT QgsProcessingParameterDefinition sipType = sipType_QgsProcessingParameterFolderDestination; else if ( sipCpp->type() == QgsProcessingParameterBand::typeName() ) sipType = sipType_QgsProcessingParameterBand; + else if ( sipCpp->type() == QgsProcessingParameterLayout::typeName() ) + sipType = sipType_QgsProcessingParameterLayout; else sipType = nullptr; SIP_END @@ -2582,6 +2584,43 @@ class CORE_EXPORT QgsProcessingParameterBand : public QgsProcessingParameterDefi bool mAllowMultiple = false; }; +/** + * \class QgsProcessingParameterLayout + * \ingroup core + * A print layout parameter, allowing users to select a print layout. + * + * QgsProcessingParameterLayout should be evaluated by calling QgsProcessingAlgorithm::parameterAsString(). + * This will return the name of the target print layout. + * + * \since QGIS 3.8 + */ +class CORE_EXPORT QgsProcessingParameterLayout : public QgsProcessingParameterDefinition +{ + public: + + /** + * Constructor for QgsProcessingParameterLayout. + */ + QgsProcessingParameterLayout( 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( "layout" ); } + QgsProcessingParameterDefinition *clone() const override SIP_FACTORY; + QString type() const override { return typeName(); } + QString valueAsPythonString( const QVariant &value, QgsProcessingContext &context ) const override; + QString asScriptCode() const override; + QString asPythonString( QgsProcessing::PythonOutputType outputType = QgsProcessing::PythonQgsProcessingAlgorithmSubclass ) const override; + + /** + * Creates a new parameter using the definition from a script code. + */ + static QgsProcessingParameterLayout *fromScriptCode( const QString &name, const QString &description, bool isOptional, const QString &definition ) SIP_FACTORY; + +}; + // clazy:excludeall=qstring-allocations #endif // QGSPROCESSINGPARAMETERS_H diff --git a/src/core/processing/qgsprocessingparametertypeimpl.h b/src/core/processing/qgsprocessingparametertypeimpl.h index 2b1c5632467..adc826e7c08 100644 --- a/src/core/processing/qgsprocessingparametertypeimpl.h +++ b/src/core/processing/qgsprocessingparametertypeimpl.h @@ -1458,4 +1458,57 @@ class CORE_EXPORT QgsProcessingParameterTypeFeatureSink : public QgsProcessingPa }; + +/** + * A print layout parameter for Processing algorithms. + * + * \ingroup core + * \note No Python bindings available. Get your copy from QgsApplication.processingRegistry().parameterType('layout') + * \since QGIS 3.8 + */ +class CORE_EXPORT QgsProcessingParameterTypeLayout : public QgsProcessingParameterType +{ + QgsProcessingParameterDefinition *create( const QString &name ) const override SIP_FACTORY + { + return new QgsProcessingParameterLayout( name ); + } + + QString description() const override + { + return QCoreApplication::translate( "Processing", "A print layout parameter." ); + } + + QString name() const override + { + return QCoreApplication::translate( "Processing", "Print Layout" ); + } + + QString id() const override + { + return QStringLiteral( "layout" ); + } + + QString pythonImportString() const override + { + return QStringLiteral( "from qgis.core import QgsProcessingParameterLayout" ); + } + + QString className() const override + { + return QStringLiteral( "QgsProcessingParameterLayout" ); + } + + QStringList acceptedPythonTypes() const override + { + return QStringList() << QObject::tr( "str: name of print layout in current project" ) + << QStringLiteral( "QgsProperty" ); + } + + QStringList acceptedStringValues() const override + { + return QStringList() << QObject::tr( "Name of print layout in current project" ); + } + + +}; #endif // QGSPROCESSINGPARAMETERTYPEIMPL_H diff --git a/src/core/processing/qgsprocessingregistry.cpp b/src/core/processing/qgsprocessingregistry.cpp index 73c63d9f73b..b1b1c64d00c 100644 --- a/src/core/processing/qgsprocessingregistry.cpp +++ b/src/core/processing/qgsprocessingregistry.cpp @@ -48,6 +48,7 @@ QgsProcessingRegistry::QgsProcessingRegistry( QObject *parent SIP_TRANSFERTHIS ) addParameterType( new QgsProcessingParameterTypeDistance() ); addParameterType( new QgsProcessingParameterTypeBand() ); addParameterType( new QgsProcessingParameterTypeFeatureSink() ); + addParameterType( new QgsProcessingParameterTypeLayout() ); } QgsProcessingRegistry::~QgsProcessingRegistry() diff --git a/tests/src/analysis/testqgsprocessing.cpp b/tests/src/analysis/testqgsprocessing.cpp index fa9d5a804d5..4c1b8b49a6d 100644 --- a/tests/src/analysis/testqgsprocessing.cpp +++ b/tests/src/analysis/testqgsprocessing.cpp @@ -558,6 +558,7 @@ class TestQgsProcessing: public QObject void parameterFileOut(); void parameterFolderOut(); void parameterBand(); + void parameterLayout(); void checkParamValues(); void combineLayerExtent(); void processingFeatureSource(); @@ -5810,6 +5811,122 @@ void TestQgsProcessing::parameterBand() QCOMPARE( fromCode->parentLayerParameterName(), def->parentLayerParameterName() ); } +void TestQgsProcessing::parameterLayout() +{ + QgsProcessingContext context; + + // not optional! + std::unique_ptr< QgsProcessingParameterLayout > def( new QgsProcessingParameterLayout( "non_optional", QString(), QString(), false ) ); + QVERIFY( def->checkValueIsAcceptable( 1 ) ); + QVERIFY( def->checkValueIsAcceptable( "test" ) ); + QVERIFY( !def->checkValueIsAcceptable( "" ) ); + QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); + + // string + QVariantMap params; + params.insert( "non_optional", QString( "abcdef" ) ); + QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QString( "abcdef" ) ); + + QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) ); + QCOMPARE( def->valueAsPythonString( 5, context ), QStringLiteral( "'5'" ) ); + QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) ); + QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc\ndef" ), context ), QStringLiteral( "'abc\\ndef'" ) ); + QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\\'complex\\' username=\\\"complex\\\"'" ) ); + QCOMPARE( def->valueAsPythonString( QStringLiteral( "c:\\test\\new data\\test.dat" ), context ), QStringLiteral( "'c:\\\\test\\\\new data\\\\test.dat'" ) ); + + QString pythonCode = def->asPythonString(); + QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterLayout('non_optional', '', defaultValue=None)" ) ); + + QString code = def->asScriptCode(); + QCOMPARE( code, QStringLiteral( "##non_optional=layout" ) ); + std::unique_ptr< QgsProcessingParameterLayout > fromCode( dynamic_cast< QgsProcessingParameterLayout * >( 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(); + QgsProcessingParameterLayout 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< QgsProcessingParameterLayout *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) ); + QVERIFY( dynamic_cast< QgsProcessingParameterLayout *>( def.get() ) ); + + fromCode.reset( dynamic_cast< QgsProcessingParameterLayout * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=layout None" ) ) ) ); + QVERIFY( fromCode.get() ); + QCOMPARE( fromCode->name(), def->name() ); + QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); + QCOMPARE( fromCode->flags(), def->flags() ); + QVERIFY( !fromCode->defaultValue().isValid() ); + + fromCode.reset( dynamic_cast< QgsProcessingParameterLayout * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=layout it's mario" ) ) ) ); + QVERIFY( fromCode.get() ); + QCOMPARE( fromCode->name(), def->name() ); + QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); + QCOMPARE( fromCode->flags(), def->flags() ); + QCOMPARE( fromCode->defaultValue().toString(), QStringLiteral( "it's mario" ) ); + + def->setDefaultValue( QStringLiteral( "it's mario" ) ); + pythonCode = def->asPythonString(); + QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterLayout('non_optional', '', defaultValue='it\\'s mario')" ) ); + + code = def->asScriptCode(); + fromCode.reset( dynamic_cast< QgsProcessingParameterLayout * >( 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() ); + + fromCode.reset( dynamic_cast< QgsProcessingParameterLayout * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=layout 'my val'" ) ) ) ); + QVERIFY( fromCode.get() ); + QCOMPARE( fromCode->name(), def->name() ); + QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); + QCOMPARE( fromCode->flags(), def->flags() ); + QCOMPARE( fromCode->defaultValue().toString(), QStringLiteral( "my val" ) ); + + fromCode.reset( dynamic_cast< QgsProcessingParameterLayout * >( QgsProcessingParameters::parameterFromScriptCode( QStringLiteral( "##non_optional=layout \"my val\"" ) ) ) ); + QVERIFY( fromCode.get() ); + QCOMPARE( fromCode->name(), def->name() ); + QCOMPARE( fromCode->description(), QStringLiteral( "non optional" ) ); + QCOMPARE( fromCode->flags(), def->flags() ); + QCOMPARE( fromCode->defaultValue().toString(), QStringLiteral( "my val" ) ); + + // optional + def.reset( new QgsProcessingParameterLayout( "optional", QString(), QString( "default" ), true ) ); + QVERIFY( def->checkValueIsAcceptable( 1 ) ); + QVERIFY( def->checkValueIsAcceptable( "test" ) ); + QVERIFY( def->checkValueIsAcceptable( "" ) ); + QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); + + params.insert( "optional", QVariant() ); + QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QString( "default" ) ); + params.insert( "optional", QString() ); //empty string should not result in default value + QCOMPARE( QgsProcessingParameters::parameterAsString( def.get(), params, context ), QString() ); + + pythonCode = def->asPythonString(); + QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterLayout('optional', '', optional=True, defaultValue='default')" ) ); + code = def->asScriptCode(); + QCOMPARE( code, QStringLiteral( "##optional=optional layout default" ) ); + fromCode.reset( dynamic_cast< QgsProcessingParameterLayout * >( 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() ); + + // not optional, valid default! + def.reset( new QgsProcessingParameterLayout( "non_optional", QString(), QString( "def" ), false ) ); + QVERIFY( def->checkValueIsAcceptable( 1 ) ); + QVERIFY( def->checkValueIsAcceptable( "test" ) ); + QVERIFY( !def->checkValueIsAcceptable( "" ) ); + QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); // should be valid, falls back to valid default +} + void TestQgsProcessing::checkParamValues() { DummyAlgorithm a( "asd" );