Skip to content

Commit

Permalink
[processsing][API] Add api to allow QgsProcessingParameterFile parame…
Browse files Browse the repository at this point in the history
…ters

to have a file filter string defined

Previously only an extension was settable, which prevented use when
multiple extension types are possible. A file filter string allows
for flexible file selection instead.
  • Loading branch information
nyalldawson committed Jul 8, 2019
1 parent 3d2d269 commit e9a1eab
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 16 deletions.
Expand Up @@ -1298,9 +1298,13 @@ An input file or folder parameter for processing algorithms.
};

QgsProcessingParameterFile( const QString &name, const QString &description = QString(), Behavior behavior = File, const QString &extension = QString(), const QVariant &defaultValue = QVariant(),
bool optional = false );
bool optional = false, const QString &fileFilter = QString() );
%Docstring
Constructor for QgsProcessingParameterFile.

The ``extension`` argument allows for specifying a file extension associated with the parameter (e.g. "html"). Use ``fileFilter``
for a more flexible approach which allows for multiple file extensions. Only one of ``extension`` or ``fileFilter`` should be specified,
if both are specified then ``fileFilter`` takes precedence.
%End

static QString typeName();
Expand Down Expand Up @@ -1335,14 +1339,48 @@ Sets the parameter ``behavior`` (e.g. File or Folder).
%Docstring
Returns any specified file extension for the parameter.

.. note::

See fileFilter() for a more flexible approach.

.. seealso:: :py:func:`setExtension`
%End

void setExtension( const QString &extension );
%Docstring
Sets a file ``extension`` for the parameter.

Calling this method resets any existing fileFilter().

.. note::

See setFileFilter() for a more flexible approach.

.. seealso:: :py:func:`extension`
%End

QString fileFilter() const;
%Docstring
Returns the file filter string for file destinations compatible with this parameter.

.. seealso:: :py:func:`setFileFilter`

.. seealso:: :py:func:`extension`

.. versionadded:: 3.10
%End

void setFileFilter( const QString &filter );
%Docstring
Sets the file ``filter`` string for file destinations compatible with this parameter.

Calling this method resets any existing extension() setting.

.. seealso:: :py:func:`fileFilter`

.. seealso:: :py:func:`setExtension`

.. versionadded:: 3.10
%End

virtual QVariantMap toVariantMap() const;
Expand Down
42 changes: 38 additions & 4 deletions src/core/processing/qgsprocessingparameters.cpp
Expand Up @@ -34,6 +34,7 @@
#include "qgslayoutmanager.h"
#include "qgsprintlayout.h"
#include "qgssymbollayerutils.h"
#include "qgsfileutils.h"
#include <functional>


Expand Down Expand Up @@ -2289,10 +2290,11 @@ QgsProcessingParameterPoint *QgsProcessingParameterPoint::fromScriptCode( const
return new QgsProcessingParameterPoint( name, description, definition, isOptional );
}

QgsProcessingParameterFile::QgsProcessingParameterFile( const QString &name, const QString &description, Behavior behavior, const QString &extension, const QVariant &defaultValue, bool optional )
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 )
, mExtension( extension )
, mExtension( fileFilter.isEmpty() ? extension : QString() )
, mFileFilter( fileFilter.isEmpty() && extension.isEmpty() ? QObject::tr( "All files (*.*)" ) : fileFilter )
{

}
Expand Down Expand Up @@ -2322,8 +2324,18 @@ bool QgsProcessingParameterFile::checkValueIsAcceptable( const QVariant &input,
case File:
{
if ( !mExtension.isEmpty() )
{
return string.endsWith( mExtension, Qt::CaseInsensitive );
return true;
}
else if ( !mFileFilter.isEmpty() )
{
const QString test = QgsFileUtils::addExtensionFromFilter( string, mFileFilter );
return test == string;
}
else
{
return true;
}
}

case Folder:
Expand Down Expand Up @@ -2353,7 +2365,10 @@ QString QgsProcessingParameterFile::asPythonString( const QgsProcessing::PythonO
if ( mFlags & FlagOptional )
code += QStringLiteral( ", optional=True" );
code += QStringLiteral( ", behavior=%1" ).arg( mBehavior == File ? QStringLiteral( "QgsProcessingParameterFile.File" ) : QStringLiteral( "QgsProcessingParameterFile.Folder" ) );
code += QStringLiteral( ", extension='%1'" ).arg( mExtension );
if ( !mExtension.isEmpty() )
code += QStringLiteral( ", extension='%1'" ).arg( mExtension );
if ( !mFileFilter.isEmpty() )
code += QStringLiteral( ", fileFilter='%1'" ).arg( mFileFilter );
QgsProcessingContext c;
code += QStringLiteral( ", defaultValue=%1)" ).arg( valueAsPythonString( mDefault, c ) );
return code;
Expand All @@ -2362,11 +2377,29 @@ QString QgsProcessingParameterFile::asPythonString( const QgsProcessing::PythonO
return QString();
}

void QgsProcessingParameterFile::setExtension( const QString &extension )
{
mExtension = extension;
mFileFilter.clear();
}

QString QgsProcessingParameterFile::fileFilter() const
{
return mFileFilter;
}

void QgsProcessingParameterFile::setFileFilter( const QString &filter )
{
mFileFilter = filter;
mExtension.clear();
}

QVariantMap QgsProcessingParameterFile::toVariantMap() const
{
QVariantMap map = QgsProcessingParameterDefinition::toVariantMap();
map.insert( QStringLiteral( "behavior" ), mBehavior );
map.insert( QStringLiteral( "extension" ), mExtension );
map.insert( QStringLiteral( "filefilter" ), mFileFilter );
return map;
}

Expand All @@ -2375,6 +2408,7 @@ bool QgsProcessingParameterFile::fromVariantMap( const QVariantMap &map )
QgsProcessingParameterDefinition::fromVariantMap( map );
mBehavior = static_cast< Behavior >( map.value( QStringLiteral( "behavior" ) ).toInt() );
mExtension = map.value( QStringLiteral( "extension" ) ).toString();
mFileFilter = map.value( QStringLiteral( "filefilter" ) ).toString();
return true;
}

Expand Down
36 changes: 34 additions & 2 deletions src/core/processing/qgsprocessingparameters.h
Expand Up @@ -1312,9 +1312,13 @@ class CORE_EXPORT QgsProcessingParameterFile : public QgsProcessingParameterDefi

/**
* Constructor for QgsProcessingParameterFile.
*
* The \a extension argument allows for specifying a file extension associated with the parameter (e.g. "html"). Use \a fileFilter
* for a more flexible approach which allows for multiple file extensions. Only one of \a extension or \a fileFilter should be specified,
* if both are specified then \a fileFilter takes precedence.
*/
QgsProcessingParameterFile( const QString &name, const QString &description = QString(), Behavior behavior = File, const QString &extension = QString(), const QVariant &defaultValue = QVariant(),
bool optional = false );
bool optional = false, const QString &fileFilter = QString() );

/**
* Returns the type name for the parameter class.
Expand All @@ -1340,15 +1344,42 @@ class CORE_EXPORT QgsProcessingParameterFile : public QgsProcessingParameterDefi

/**
* Returns any specified file extension for the parameter.
*
* \note See fileFilter() for a more flexible approach.
*
* \see setExtension()
*/
QString extension() const { return mExtension; }

/**
* Sets a file \a extension for the parameter.
*
* Calling this method resets any existing fileFilter().
*
* \note See setFileFilter() for a more flexible approach.
*
* \see extension()
*/
void setExtension( const QString &extension );

/**
* Returns the file filter string for file destinations compatible with this parameter.
* \see setFileFilter()
* \see extension()
* \since QGIS 3.10
*/
void setExtension( const QString &extension ) { mExtension = extension; }
QString fileFilter() const;

/**
* Sets the file \a filter string for file destinations compatible with this parameter.
*
* Calling this method resets any existing extension() setting.
*
* \see fileFilter()
* \see setExtension()
* \since QGIS 3.10
*/
void setFileFilter( const QString &filter );

QVariantMap toVariantMap() const override;
bool fromVariantMap( const QVariantMap &map ) override;
Expand All @@ -1362,6 +1393,7 @@ class CORE_EXPORT QgsProcessingParameterFile : public QgsProcessingParameterDefi

Behavior mBehavior = File;
QString mExtension;
QString mFileFilter;
};

/**
Expand Down
4 changes: 3 additions & 1 deletion src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp
Expand Up @@ -1304,7 +1304,9 @@ QWidget *QgsProcessingFileWidgetWrapper::createWidget()
{
case QgsProcessingParameterFile::File:
mFileWidget->setStorageMode( QgsFileWidget::GetFile );
if ( !fileParam->extension().isEmpty() )
if ( !fileParam->fileFilter().isEmpty() )
mFileWidget->setFilter( fileParam->fileFilter() );
else if ( !fileParam->extension().isEmpty() )
mFileWidget->setFilter( tr( "%1 files" ).arg( fileParam->extension().toUpper() ) + QStringLiteral( " (*." ) + fileParam->extension().toLower() + ')' );
break;

Expand Down
45 changes: 43 additions & 2 deletions tests/src/analysis/testqgsprocessing.cpp
Expand Up @@ -3017,6 +3017,47 @@ void TestQgsProcessing::parameterFile()
QCOMPARE( fromMap.flags(), def->flags() );
QCOMPARE( fromMap.defaultValue(), def->defaultValue() );
QCOMPARE( fromMap.extension(), def->extension() );
QCOMPARE( fromMap.fileFilter(), def->fileFilter() );
QCOMPARE( fromMap.behavior(), def->behavior() );
def.reset( dynamic_cast< QgsProcessingParameterFile *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
QVERIFY( dynamic_cast< QgsProcessingParameterFile *>( def.get() ) );

// with file filter
def.reset( new QgsProcessingParameterFile( "non_optional", QString(), QgsProcessingParameterFile::File, QStringLiteral( ".bmp" ), QString( "abc.bmp" ), false, QStringLiteral( "PNG Files (*.png)" ) ) );
QCOMPARE( def->fileFilter(), QStringLiteral( "PNG Files (*.png)" ) );
QVERIFY( def->extension().isEmpty() );
QVERIFY( def->checkValueIsAcceptable( "bricks.png" ) );
QVERIFY( def->checkValueIsAcceptable( "bricks.PNG" ) );
QVERIFY( !def->checkValueIsAcceptable( "bricks.pcx" ) );

QCOMPARE( def->valueAsPythonString( QVariant(), context ), QStringLiteral( "None" ) );
QCOMPARE( def->valueAsPythonString( "bricks.png", context ), QStringLiteral( "'bricks.png'" ) );
QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) );
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'" ) );

pythonCode = def->asPythonString();
QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterFile('non_optional', '', behavior=QgsProcessingParameterFile.File, fileFilter='PNG Files (*.png)', defaultValue='abc.bmp')" ) );

code = def->asScriptCode();
QCOMPARE( code, QStringLiteral( "##non_optional=file abc.bmp" ) );
fromCode.reset( dynamic_cast< QgsProcessingParameterFile * >( 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() );
QCOMPARE( fromCode->behavior(), def->behavior() );

map = def->toVariantMap();
fromMap = QgsProcessingParameterFile( "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() );
QCOMPARE( fromMap.extension(), def->extension() );
QCOMPARE( fromMap.fileFilter(), def->fileFilter() );
QCOMPARE( fromMap.behavior(), def->behavior() );
def.reset( dynamic_cast< QgsProcessingParameterFile *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) );
QVERIFY( dynamic_cast< QgsProcessingParameterFile *>( def.get() ) );
Expand All @@ -3035,7 +3076,7 @@ void TestQgsProcessing::parameterFile()
QCOMPARE( QgsProcessingParameters::parameterAsFile( def.get(), params, context ), QString( "gef.bmp" ) );

pythonCode = def->asPythonString();
QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterFile('optional', '', optional=True, behavior=QgsProcessingParameterFile.File, extension='', defaultValue='gef.bmp')" ) );
QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterFile('optional', '', optional=True, behavior=QgsProcessingParameterFile.File, fileFilter='All files (*.*)', defaultValue='gef.bmp')" ) );

code = def->asScriptCode();
QCOMPARE( code, QStringLiteral( "##optional=optional file gef.bmp" ) );
Expand All @@ -3050,7 +3091,7 @@ void TestQgsProcessing::parameterFile()
// folder
def.reset( new QgsProcessingParameterFile( "optional", QString(), QgsProcessingParameterFile::Folder, QString(), QString( "/home/me" ), true ) );
pythonCode = def->asPythonString();
QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterFile('optional', '', optional=True, behavior=QgsProcessingParameterFile.Folder, extension='', defaultValue='/home/me')" ) );
QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterFile('optional', '', optional=True, behavior=QgsProcessingParameterFile.Folder, fileFilter='All files (*.*)', defaultValue='/home/me')" ) );
code = def->asScriptCode();
QCOMPARE( code, QStringLiteral( "##optional=optional folder /home/me" ) );
fromCode.reset( dynamic_cast< QgsProcessingParameterFile * >( QgsProcessingParameters::parameterFromScriptCode( code ) ) );
Expand Down
20 changes: 14 additions & 6 deletions tests/src/gui/testprocessinggui.cpp
Expand Up @@ -962,7 +962,7 @@ void TestProcessingGui::testFileWrapper()
QCOMPARE( spy.count(), 1 );
QCOMPARE( wrapper.widgetValue().toString(), TEST_DATA_DIR + QStringLiteral( "/points.shp" ) );
QCOMPARE( static_cast< QgsFileWidget * >( wrapper.wrappedWidget() )->filePath(), TEST_DATA_DIR + QStringLiteral( "/points.shp" ) );
QVERIFY( static_cast< QgsFileWidget * >( wrapper.wrappedWidget() )->filter().isEmpty() );
QCOMPARE( static_cast< QgsFileWidget * >( wrapper.wrappedWidget() )->filter(), QStringLiteral( "All files (*.*)" ) );
QCOMPARE( static_cast< QgsFileWidget * >( wrapper.wrappedWidget() )->storageMode(), QgsFileWidget::GetFile );
wrapper.setWidgetValue( QString(), context );
QCOMPARE( spy.count(), 2 );
Expand All @@ -988,20 +988,28 @@ void TestProcessingGui::testFileWrapper()

delete w;

// with filter
// with extension
QgsProcessingParameterFile param2( QStringLiteral( "file" ), QStringLiteral( "file" ), QgsProcessingParameterFile::File, QStringLiteral( "qml" ) );

QgsProcessingFileWidgetWrapper wrapper2( &param2, type );
w = wrapper2.createWrappedWidget( context );
QCOMPARE( static_cast< QgsFileWidget * >( wrapper2.wrappedWidget() )->filter(), QStringLiteral( "QML files (*.qml)" ) );
QCOMPARE( static_cast< QgsFileWidget * >( wrapper2.wrappedWidget() )->storageMode(), QgsFileWidget::GetFile );

// folder mode
QgsProcessingParameterFile param3( QStringLiteral( "folder" ), QStringLiteral( "folder" ), QgsProcessingParameterFile::Folder );
// with filter
QgsProcessingParameterFile param3( QStringLiteral( "file" ), QStringLiteral( "file" ), QgsProcessingParameterFile::File, QString(), QVariant(), false, QStringLiteral( "Project files (*.qgs *.qgz)" ) );

QgsProcessingFileWidgetWrapper wrapper3( &param3, type );
QgsProcessingFileWidgetWrapper wrapper3( & param3, type );
w = wrapper3.createWrappedWidget( context );
QCOMPARE( static_cast< QgsFileWidget * >( wrapper3.wrappedWidget() )->storageMode(), QgsFileWidget::GetDirectory );
QCOMPARE( static_cast< QgsFileWidget * >( wrapper3.wrappedWidget() )->filter(), QStringLiteral( "Project files (*.qgs *.qgz)" ) );
QCOMPARE( static_cast< QgsFileWidget * >( wrapper3.wrappedWidget() )->storageMode(), QgsFileWidget::GetFile );

// folder mode
QgsProcessingParameterFile param4( QStringLiteral( "folder" ), QStringLiteral( "folder" ), QgsProcessingParameterFile::Folder );

QgsProcessingFileWidgetWrapper wrapper4( &param4, type );
w = wrapper4.createWrappedWidget( context );
QCOMPARE( static_cast< QgsFileWidget * >( wrapper4.wrappedWidget() )->storageMode(), QgsFileWidget::GetDirectory );
};

// standard wrapper
Expand Down

0 comments on commit e9a1eab

Please sign in to comment.