Skip to content

Commit

Permalink
Merge pull request #5062 from boundlessgeo/raster-widget-multiple-select
Browse files Browse the repository at this point in the history
Allow multiple raster selection from GDAL source select widget
  • Loading branch information
elpaso committed Aug 24, 2017
2 parents 50e8e1c + b947406 commit 835e6d2
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 42 deletions.
17 changes: 14 additions & 3 deletions python/gui/qgsfilewidget.sip
Expand Up @@ -30,7 +30,8 @@ class QgsFileWidget : QWidget
enum StorageMode
{
GetFile,
GetDirectory
GetDirectory,
GetMultipleFiles,
};

enum RelativeStorage
Expand All @@ -47,10 +48,20 @@ class QgsFileWidget : QWidget

QString filePath();
%Docstring
Returns the current file path
Returns the current file path(s)
when multiple files are selected, they are quoted and separated
by a single space (for example: '"/path/foo" "path/bar"')
.. seealso:: filePaths
:rtype: str
%End

static QStringList splitFilePaths( const QString &path );
%Docstring
Split the the quoted and space separated ``path`` and returns a QString list
.. seealso:: filePath
:rtype: list of str
%End

void setFilePath( QString path );
%Docstring
Sets the file path
Expand All @@ -72,7 +83,7 @@ returns the open file dialog title
setDialogTitle defines the open file dialog title
.. note::

if not defined, the title is "Select a file" or "Select a directory" depending on the configuration.
if not defined, the title is "Select a file" or "Select a directory" or "Select one or more files" depending on the configuration.
%End

QString filter() const;
Expand Down
125 changes: 97 additions & 28 deletions src/gui/qgsfilewidget.cpp
Expand Up @@ -77,6 +77,16 @@ QString QgsFileWidget::filePath()
return mFilePath;
}

QStringList QgsFileWidget::splitFilePaths( const QString &path )
{
QStringList paths;
for ( auto pathsPart : path.split( QRegExp( "\"\\s+\"" ), QString::SkipEmptyParts ) )
{
paths.append( pathsPart.remove( QRegExp( "(^\\s*\")|(\"\\s*)" ) ) );
}
return paths;
}

void QgsFileWidget::setFilePath( QString path )
{
if ( path == QgsApplication::nullRepresentation() )
Expand All @@ -86,6 +96,7 @@ void QgsFileWidget::setFilePath( QString path )

//will trigger textEdited slot
mLineEdit->setValue( path );

}

void QgsFileWidget::setReadOnly( bool readOnly )
Expand Down Expand Up @@ -130,6 +141,15 @@ void QgsFileWidget::textEdited( const QString &path )
{
mFilePath = path;
mLinkLabel->setText( toUrl( path ) );
// Show tooltip if multiple files are selected
if ( path.contains( QStringLiteral( "\" \"" ) ) )
{
mLineEdit->setToolTip( tr( "Selected files:<br><ul><li>%1</li></ul><br>" ).arg( splitFilePaths( path ).join( QStringLiteral( "</li><li>" ) ) ) );
}
else
{
mLineEdit->setToolTip( QString() );
}
emit fileChanged( mFilePath );
}

Expand Down Expand Up @@ -231,39 +251,75 @@ void QgsFileWidget::openFileDialog()

// Handle Storage
QString fileName;
QStringList fileNames;
QString title;
if ( mStorageMode == GetFile )
{
title = !mDialogTitle.isEmpty() ? mDialogTitle : tr( "Select a file" );
fileName = QFileDialog::getOpenFileName( this, title, QFileInfo( oldPath ).absoluteFilePath(), mFilter );
}
else if ( mStorageMode == GetDirectory )

switch ( mStorageMode )
{
title = !mDialogTitle.isEmpty() ? mDialogTitle : tr( "Select a directory" );
fileName = QFileDialog::getExistingDirectory( this, title, QFileInfo( oldPath ).absoluteFilePath(), QFileDialog::ShowDirsOnly );
case GetFile:
title = !mDialogTitle.isEmpty() ? mDialogTitle : tr( "Select a file" );
fileName = QFileDialog::getOpenFileName( this, title, QFileInfo( oldPath ).absoluteFilePath(), mFilter );
break;
case GetMultipleFiles:
title = !mDialogTitle.isEmpty() ? mDialogTitle : tr( "Select one ore more files" );
fileNames = QFileDialog::getOpenFileNames( this, title, QFileInfo( oldPath ).absoluteFilePath(), mFilter );
break;
case GetDirectory:
title = !mDialogTitle.isEmpty() ? mDialogTitle : tr( "Select a directory" );
fileName = QFileDialog::getExistingDirectory( this, title, QFileInfo( oldPath ).absoluteFilePath(), QFileDialog::ShowDirsOnly );
break;
}

if ( fileName.isEmpty() )
if ( fileName.isEmpty() && fileNames.isEmpty( ) )
return;

if ( mStorageMode != GetMultipleFiles )
{
fileName = QDir::toNativeSeparators( QDir::cleanPath( QFileInfo( fileName ).absoluteFilePath() ) );
}
else
{
for ( int i = 0; i < fileNames.length(); i++ )
{
fileNames.replace( i, QDir::toNativeSeparators( QDir::cleanPath( QFileInfo( fileNames.at( i ) ).absoluteFilePath() ) ) ) ;
}
}

fileName = QDir::toNativeSeparators( QDir::cleanPath( QFileInfo( fileName ).absoluteFilePath() ) );
// Store the last used path:
switch ( mStorageMode )
{
case GetFile:
settings.setValue( QStringLiteral( "UI/lastFileNameWidgetDir" ), QFileInfo( fileName ).absolutePath() );
break;
case GetDirectory:
settings.setValue( QStringLiteral( "UI/lastFileNameWidgetDir" ), fileName );
break;
case GetMultipleFiles:
settings.setValue( QStringLiteral( "UI/lastFileNameWidgetDir" ), QFileInfo( fileNames.first( ) ).absolutePath() );
break;
}

if ( mStorageMode == GetFile )
// Handle relative Path storage
if ( mStorageMode != GetMultipleFiles )
{
settings.setValue( QStringLiteral( "UI/lastFileNameWidgetDir" ), QFileInfo( fileName ).absolutePath() );
fileName = relativePath( fileName, true );
setFilePath( fileName );
}
else if ( mStorageMode == GetDirectory )
else
{
settings.setValue( QStringLiteral( "UI/lastFileNameWidgetDir" ), fileName );
for ( int i = 0; i < fileNames.length(); i++ )
{
fileNames.replace( i, relativePath( fileNames.at( i ), true ) );
}
if ( fileNames.length() > 1 )
{
setFilePath( QStringLiteral( "\"%1\"" ).arg( fileNames.join( "\" \"" ) ) );
}
else
{
setFilePath( fileNames.first( ) );
}
}

// Handle relative Path storage
fileName = relativePath( fileName, true );

// Keep the new value
setFilePath( fileName );
}


Expand Down Expand Up @@ -327,7 +383,6 @@ QString QgsFileWidget::toUrl( const QString &path ) const




///@cond PRIVATE


Expand Down Expand Up @@ -359,16 +414,30 @@ void QgsFileDropEdit::setFilters( const QString &filters )

QString QgsFileDropEdit::acceptableFilePath( QDropEvent *event ) const
{
QString path;
QStringList paths;
if ( event->mimeData()->hasUrls() )
{
QFileInfo file( event->mimeData()->urls().first().toLocalFile() );
if ( ( mStorageMode == QgsFileWidget::GetFile && file.isFile() &&
( mAcceptableExtensions.isEmpty() || mAcceptableExtensions.contains( file.suffix(), Qt::CaseInsensitive ) ) )
|| ( mStorageMode == QgsFileWidget::GetDirectory && file.isDir() ) )
path = file.filePath();
Q_FOREACH ( const QUrl &url, event->mimeData()->urls() )
{
QFileInfo file( url.toLocalFile() );
if ( ( mStorageMode != QgsFileWidget::GetDirectory && file.isFile() &&
( mAcceptableExtensions.isEmpty() || mAcceptableExtensions.contains( file.suffix(), Qt::CaseInsensitive ) ) )
|| ( mStorageMode == QgsFileWidget::GetDirectory && file.isDir() ) )
paths.append( file.filePath() );
}
}
if ( paths.size() > 1 )
{
return QStringLiteral( "\"%1\"" ).arg( paths.join( "\" \"" ) );
}
else if ( paths.size() == 1 )
{
return paths.first();
}
else
{
return QString();
}
return path;
}

void QgsFileDropEdit::dragEnterEvent( QDragEnterEvent *event )
Expand Down
24 changes: 17 additions & 7 deletions src/gui/qgsfilewidget.h
Expand Up @@ -61,8 +61,9 @@ class GUI_EXPORT QgsFileWidget : public QWidget
*/
enum StorageMode
{
GetFile,
GetDirectory
GetFile, //! Select a single file
GetDirectory, //! Select a directory
GetMultipleFiles, //! Select multiple files
};

/**
Expand All @@ -80,9 +81,20 @@ class GUI_EXPORT QgsFileWidget : public QWidget
*/
explicit QgsFileWidget( QWidget *parent SIP_TRANSFERTHIS = nullptr );

//! Returns the current file path
/**
* \brief Returns the current file path(s)
* when multiple files are selected, they are quoted and separated
* by a single space (for example: '"/path/foo" "path/bar"')
* \see filePaths
*/
QString filePath();

/**
* \brief Split the the quoted and space separated \a path and returns a QString list
* \see filePath
*/
static QStringList splitFilePaths( const QString &path );

//! Sets the file path
void setFilePath( QString path );

Expand All @@ -94,7 +106,7 @@ class GUI_EXPORT QgsFileWidget : public QWidget

/**
* \brief setDialogTitle defines the open file dialog title
* \note if not defined, the title is "Select a file" or "Select a directory" depending on the configuration.
* \note if not defined, the title is "Select a file" or "Select a directory" or "Select one or more files" depending on the configuration.
*/
void setDialogTitle( const QString &title );

Expand Down Expand Up @@ -211,9 +223,7 @@ class GUI_EXPORT QgsFileDropEdit: public QgsFilterLineEdit

private:

/**
Return file name if object meets drop criteria.
*/
//! Return file name if object meets drop criteria.
QString acceptableFilePath( QDropEvent *event ) const;

QStringList mAcceptableExtensions;
Expand Down
7 changes: 5 additions & 2 deletions src/providers/gdal/qgsgdalsourceselect.cpp
Expand Up @@ -24,6 +24,7 @@ QgsGdalSourceSelect::QgsGdalSourceSelect( QWidget *parent, Qt::WindowFlags fl, Q
setupUi( this );
setupButtons( buttonBox );
mQgsFileWidget->setFilter( QgsProviderRegistry::instance()->fileRasterFilters() );
mQgsFileWidget->setStorageMode( QgsFileWidget::GetMultipleFiles );
connect( mQgsFileWidget, &QgsFileWidget::fileChanged, this, [ = ]( const QString & path )
{
mRasterPath = path;
Expand All @@ -38,8 +39,10 @@ QgsGdalSourceSelect::~QgsGdalSourceSelect()

void QgsGdalSourceSelect::addButtonClicked()
{
QFileInfo baseName( mRasterPath );
emit addRasterLayer( mRasterPath, baseName.baseName(), QStringLiteral( "gdal" ) );
Q_FOREACH ( const QString &path, QgsFileWidget::splitFilePaths( mRasterPath ) )
{
emit addRasterLayer( path, QFileInfo( path ).baseName(), QStringLiteral( "gdal" ) );
}
}

QGISEXTERN QgsGdalSourceSelect *selectWidget( QWidget *parent, Qt::WindowFlags fl, QgsProviderRegistry::WidgetMode widgetMode )
Expand Down
35 changes: 33 additions & 2 deletions tests/src/gui/testqgsfilewidget.cpp
@@ -1,5 +1,5 @@
/***************************************************************************
testqgsdoublespinbox.cpp
testqgsfilewidget.cpp
--------------------------------------
Date : December 2014
Copyright : (C) 2014 Nyall Dawson
Expand Down Expand Up @@ -27,10 +27,11 @@ class TestQgsFileWidget: public QObject
void cleanupTestCase(); // will be called after the last testfunction was executed.
void init(); // will be called before each testfunction is executed.
void cleanup(); // will be called after every testfunction.

void relativePath();
void toUrl();
void testDroppedFiles();
void testMultipleFiles();
void testSplitFilePaths();

};

Expand Down Expand Up @@ -140,5 +141,35 @@ void TestQgsFileWidget::testDroppedFiles()

}

void TestQgsFileWidget::testMultipleFiles()
{
QgsFileWidget *w = new QgsFileWidget();
w->setStorageMode( QgsFileWidget::GetMultipleFiles );

std::unique_ptr< QMimeData > mime( new QMimeData() );
mime->setUrls( QList<QUrl>() << QUrl::fromLocalFile( TEST_DATA_DIR + QStringLiteral( "/bug5598.shp" ) )
<< QUrl::fromLocalFile( TEST_DATA_DIR + QStringLiteral( "/bug5598.shp" ) ) );
std::unique_ptr< QDropEvent > event( new QDropEvent( QPointF( 1, 1 ), Qt::CopyAction, mime.get(), Qt::LeftButton, Qt::NoModifier ) );

qobject_cast< QgsFileDropEdit * >( w->lineEdit() )->dropEvent( event.get() );
QCOMPARE( w->lineEdit()->text(), QStringLiteral( "\"%1\" \"%1\"" ).arg( TEST_DATA_DIR + QStringLiteral( "/bug5598.shp" ) ) );
}



void TestQgsFileWidget::testSplitFilePaths()
{
const QString path = QString( TEST_DATA_DIR + QStringLiteral( "/bug5598.shp" ) );
QCOMPARE( QgsFileWidget::splitFilePaths( QStringLiteral( "\"%1\" \"%1\"" ).arg( path ) ), QStringList() << path << path );
QCOMPARE( QgsFileWidget::splitFilePaths( QStringLiteral( "\"%1\" \"%1\"" ).arg( path ) ), QStringList() << path << path );
QCOMPARE( QgsFileWidget::splitFilePaths( QStringLiteral( " \"%1\" \"%1\"" ).arg( path ) ), QStringList() << path << path );
QCOMPARE( QgsFileWidget::splitFilePaths( QStringLiteral( " \"%1\" \"%1\" " ).arg( path ) ), QStringList() << path << path );
QCOMPARE( QgsFileWidget::splitFilePaths( QStringLiteral( "\"%1\" \"%1\" " ).arg( path ) ), QStringList() << path << path );
QCOMPARE( QgsFileWidget::splitFilePaths( path ), QStringList() << path );
}




QGSTEST_MAIN( TestQgsFileWidget )
#include "testqgsfilewidget.moc"

0 comments on commit 835e6d2

Please sign in to comment.