Merge pull request #7298 from nyalldawson/layout

[layouts] Save last used export folder in project
This commit is contained in:
Matthias Kuhn 2018-06-22 14:35:37 +02:00 committed by GitHub
commit 2718317e85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 189 additions and 77 deletions

View File

@ -75,6 +75,14 @@ for filenames with an '_' character.
.. warning::
This method strips slashes from the filename, so it is safe to call with file names only, not complete paths.
%End
static QString findClosestExistingPath( const QString &path );
%Docstring
Returns the top-most existing folder from ``path``. E.g. if ``path`` is "/home/user/projects/2018/P4343"
and "/home/user/projects" exists but no "2018" subfolder exists, then the function will return "/home/user/projects".
.. versionadded:: 3.2
%End
};

View File

@ -57,7 +57,7 @@ Returns the layout view utilized by the designer.
Returns the designer's message bar.
%End
virtual void selectItems( QList< QgsLayoutItem * > items ) = 0;
virtual void selectItems( const QList< QgsLayoutItem * > &items ) = 0;
%Docstring
Selects the specified ``items``.
%End

View File

@ -120,7 +120,7 @@ QgsMessageBar *QgsAppLayoutDesignerInterface::messageBar()
return mDesigner->messageBar();
}
void QgsAppLayoutDesignerInterface::selectItems( const QList<QgsLayoutItem *> items )
void QgsAppLayoutDesignerInterface::selectItems( const QList<QgsLayoutItem *> &items )
{
mDesigner->selectItems( items );
}
@ -246,7 +246,7 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla
connect( mActionOptions, &QAction::triggered, this, [ = ]
{
QgisApp::instance()->showOptionsDialog( this, QString( "mOptionsPageComposer" ) );
QgisApp::instance()->showOptionsDialog( this, QStringLiteral( "mOptionsPageComposer" ) );
} );
mView = new QgsLayoutView();
@ -322,7 +322,7 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla
mActionsToolbar->addWidget( resizeToolButton );
QToolButton *atlasExportToolButton = new QToolButton( mAtlasToolbar );
atlasExportToolButton->setIcon( QgsApplication::getThemeIcon( "mActionExport.svg" ) );
atlasExportToolButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionExport.svg" ) ) );
atlasExportToolButton->setPopupMode( QToolButton::InstantPopup );
atlasExportToolButton->setAutoRaise( true );
atlasExportToolButton->setToolButtonStyle( Qt::ToolButtonIconOnly );
@ -1462,7 +1462,7 @@ void QgsLayoutDesignerDialog::updateStatusZoom()
zoomLevel = mView->transform().m11() * 100 / scale100;
}
whileBlocking( mStatusZoomCombo )->lineEdit()->setText( tr( "%1%" ).arg( zoomLevel, 0, 'f', 1 ) );
whileBlocking( mStatusZoomSlider )->setValue( zoomLevel );
whileBlocking( mStatusZoomSlider )->setValue( static_cast< int >( zoomLevel ) );
}
void QgsLayoutDesignerDialog::updateStatusCursorPos( QPointF position )
@ -1544,7 +1544,7 @@ void QgsLayoutDesignerDialog::dockVisibilityChanged( bool visible )
}
}
void QgsLayoutDesignerDialog::undoRedoOccurredForItems( const QSet<QString> itemUuids )
void QgsLayoutDesignerDialog::undoRedoOccurredForItems( const QSet<QString> &itemUuids )
{
mBlockItemOptions = true;
@ -1831,14 +1831,17 @@ void QgsLayoutDesignerDialog::exportToRaster()
if ( !showFileSizeWarning() )
return;
QgsSettings s;
QString outputFileName = QgsFileUtils::stringToSafeFilename( mMasterLayout->name() );
QString outputFileName;
QgsLayoutAtlas *printAtlas = atlas();
QString lastUsedDir = defaultExportPath();
if ( printAtlas && printAtlas->enabled() && mActionAtlasPreview->isChecked() )
{
QString lastUsedDir = s.value( QStringLiteral( "lastSaveAsImageDir" ), QDir::homePath(), QgsSettings::App ).toString();
outputFileName = QDir( lastUsedDir ).filePath( QgsFileUtils::stringToSafeFilename( printAtlas->currentFilename() ) );
}
else
{
outputFileName = QDir( lastUsedDir ).filePath( QgsFileUtils::stringToSafeFilename( mMasterLayout->name() ) );
}
#ifdef Q_OS_MAC
QgisApp::instance()->activateWindow();
@ -1852,6 +1855,8 @@ void QgsLayoutDesignerDialog::exportToRaster()
return;
}
setLastExportPath( fileNExt.first );
QgsLayoutExporter::ImageExportSettings settings;
QSize imageSize;
if ( !getRasterExportSettings( settings, imageSize ) )
@ -1921,19 +1926,17 @@ void QgsLayoutDesignerDialog::exportToPdf()
showForceVectorWarning();
}
QgsSettings settings;
QString lastUsedFile = settings.value( QStringLiteral( "lastSaveAsPdfFile" ), QStringLiteral( "qgis.pdf" ), QgsSettings::App ).toString();
QFileInfo file( lastUsedFile );
const QString exportPath = defaultExportPath();
QString outputFileName;
QgsLayoutAtlas *printAtlas = atlas();
if ( printAtlas && printAtlas->enabled() && mActionAtlasPreview->isChecked() )
{
outputFileName = QDir( file.path() ).filePath( QgsFileUtils::stringToSafeFilename( printAtlas->currentFilename() ) + QStringLiteral( ".pdf" ) );
outputFileName = QDir( exportPath ).filePath( QgsFileUtils::stringToSafeFilename( printAtlas->currentFilename() ) + QStringLiteral( ".pdf" ) );
}
else
{
outputFileName = file.path() + '/' + QgsFileUtils::stringToSafeFilename( mMasterLayout->name() ) + QStringLiteral( ".pdf" );
outputFileName = exportPath + '/' + QgsFileUtils::stringToSafeFilename( mMasterLayout->name() ) + QStringLiteral( ".pdf" );
}
#ifdef Q_OS_MAC
@ -1956,7 +1959,7 @@ void QgsLayoutDesignerDialog::exportToPdf()
outputFileName += QLatin1String( ".pdf" );
}
settings.setValue( QStringLiteral( "lastSaveAsPdfFile" ), outputFileName, QgsSettings::App );
setLastExportPath( outputFileName );
mView->setPaintingEnabled( false );
QgsTemporaryCursorOverride cursorOverride( Qt::BusyCursor );
@ -2025,19 +2028,17 @@ void QgsLayoutDesignerDialog::exportToSvg()
showSvgExportWarning();
QgsSettings settings;
QString lastUsedFile = settings.value( QStringLiteral( "lastSaveAsSvgFile" ), QStringLiteral( "qgis.svg" ), QgsSettings::App ).toString();
QFileInfo file( lastUsedFile );
const QString defaultPath = defaultExportPath();
QString outputFileName = QgsFileUtils::stringToSafeFilename( mMasterLayout->name() );
QgsLayoutAtlas *printAtlas = atlas();
if ( printAtlas && printAtlas->enabled() && mActionAtlasPreview->isChecked() )
{
outputFileName = QDir( file.path() ).filePath( QgsFileUtils::stringToSafeFilename( printAtlas->currentFilename() + QStringLiteral( ".svg" ) ) );
outputFileName = QDir( defaultPath ).filePath( QgsFileUtils::stringToSafeFilename( printAtlas->currentFilename() + QStringLiteral( ".svg" ) ) );
}
else
{
outputFileName = file.path() + '/' + QgsFileUtils::stringToSafeFilename( mMasterLayout->name() ) + QStringLiteral( ".svg" );
outputFileName = defaultPath + '/' + QgsFileUtils::stringToSafeFilename( mMasterLayout->name() ) + QStringLiteral( ".svg" );
}
#ifdef Q_OS_MAC
@ -2061,7 +2062,7 @@ void QgsLayoutDesignerDialog::exportToSvg()
}
bool prevSettingLabelsAsOutlines = mLayout->project()->readBoolEntry( QStringLiteral( "PAL" ), QStringLiteral( "/DrawOutlineLabels" ), true );
settings.setValue( QStringLiteral( "lastSaveAsSvgFile" ), outputFileName, QgsSettings::App );
setLastExportPath( outputFileName );
QgsLayoutExporter::SvgExportSettings svgSettings;
bool exportAsText = false;
@ -2327,7 +2328,7 @@ void QgsLayoutDesignerDialog::printAtlas()
progressDialog->setWindowTitle( tr( "Printing Atlas" ) );
connect( feedback.get(), &QgsFeedback::progressChanged, this, [ & ]( double progress )
{
progressDialog->setValue( progress );
progressDialog->setValue( static_cast< int >( progress ) );
progressDialog->setLabelText( feedback->property( "progress" ).toString() ) ;
#ifdef Q_OS_LINUX
@ -2437,8 +2438,7 @@ void QgsLayoutDesignerDialog::exportAtlasToRaster()
printAtlas->setFilenameExpression( QStringLiteral( "'output_'||@atlas_featurenumber" ), error );
}
QgsSettings s;
QString lastUsedDir = s.value( QStringLiteral( "lastSaveAtlasAsImagesDir" ), QDir::homePath(), QgsSettings::App ).toString();
QString lastUsedDir = defaultExportPath();
QFileDialog dlg( this, tr( "Export Atlas to Directory" ) );
dlg.setFileMode( QFileDialog::Directory );
@ -2461,7 +2461,7 @@ void QgsLayoutDesignerDialog::exportAtlasToRaster()
{
return;
}
s.setValue( QStringLiteral( "lastSaveAtlasAsImagesDir" ), dir, QgsSettings::App );
setLastExportPath( dir );
// test directory (if it exists and is writable)
if ( !QDir( dir ).exists() || !QFileInfo( dir ).isWritable() )
@ -2498,7 +2498,7 @@ void QgsLayoutDesignerDialog::exportAtlasToRaster()
progressDialog->setWindowTitle( tr( "Exporting Atlas" ) );
connect( feedback.get(), &QgsFeedback::progressChanged, this, [ & ]( double progress )
{
progressDialog->setValue( progress );
progressDialog->setValue( static_cast< int >( progress ) );
progressDialog->setLabelText( feedback->property( "progress" ).toString() ) ;
#ifdef Q_OS_LINUX
@ -2592,8 +2592,7 @@ void QgsLayoutDesignerDialog::exportAtlasToSvg()
printAtlas->setFilenameExpression( QStringLiteral( "'output_'||@atlas_featurenumber" ), error );
}
QgsSettings s;
QString lastUsedDir = s.value( QStringLiteral( "lastSaveAtlasAsSvgDir" ), QDir::homePath(), QgsSettings::App ).toString();
QString lastUsedDir = defaultExportPath();
QFileDialog dlg( this, tr( "Export Atlas to Directory" ) );
dlg.setFileMode( QFileDialog::Directory );
@ -2619,7 +2618,7 @@ void QgsLayoutDesignerDialog::exportAtlasToSvg()
{
return;
}
s.setValue( QStringLiteral( "lastSaveAtlasAsSvgDir" ), dir, QgsSettings::App );
setLastExportPath( dir );
// test directory (if it exists and is writable)
if ( !QDir( dir ).exists() || !QFileInfo( dir ).isWritable() )
@ -2649,7 +2648,7 @@ void QgsLayoutDesignerDialog::exportAtlasToSvg()
progressDialog->setWindowTitle( tr( "Exporting Atlas" ) );
connect( feedback.get(), &QgsFeedback::progressChanged, this, [ & ]( double progress )
{
progressDialog->setValue( progress );
progressDialog->setValue( static_cast< int >( progress ) );
progressDialog->setLabelText( feedback->property( "progress" ).toString() ) ;
#ifdef Q_OS_LINUX
@ -2754,13 +2753,11 @@ void QgsLayoutDesignerDialog::exportAtlasToPdf()
bool singleFile = mLayout->customProperty( QStringLiteral( "singleFile" ), true ).toBool();
QString outputFileName;
QgsSettings settings;
QString dir;
if ( singleFile )
{
QString lastUsedFile = settings.value( QStringLiteral( "lastSaveAsPdfFile" ), QStringLiteral( "qgis.pdf" ), QgsSettings::App ).toString();
QFileInfo file( lastUsedFile );
outputFileName = file.path() + '/' + QgsFileUtils::stringToSafeFilename( mMasterLayout->name() ) + QStringLiteral( ".pdf" );
const QString defaultPath = defaultExportPath();
outputFileName = defaultPath + '/' + QgsFileUtils::stringToSafeFilename( mMasterLayout->name() ) + QStringLiteral( ".pdf" );
#ifdef Q_OS_MAC
QgisApp::instance()->activateWindow();
@ -2781,7 +2778,7 @@ void QgsLayoutDesignerDialog::exportAtlasToPdf()
{
outputFileName += QLatin1String( ".pdf" );
}
settings.setValue( QStringLiteral( "lastSaveAsPdfFile" ), outputFileName, QgsSettings::App );
setLastExportPath( outputFileName );
}
else
{
@ -2800,7 +2797,7 @@ void QgsLayoutDesignerDialog::exportAtlasToPdf()
}
QString lastUsedDir = settings.value( QStringLiteral( "lastSaveAtlasAsPdfDir" ), QDir::homePath(), QgsSettings::App ).toString();
const QString lastUsedDir = defaultExportPath();
QFileDialog dlg( this, tr( "Export Atlas to Directory" ) );
dlg.setFileMode( QFileDialog::Directory );
@ -2826,7 +2823,7 @@ void QgsLayoutDesignerDialog::exportAtlasToPdf()
{
return;
}
settings.setValue( QStringLiteral( "lastSaveAtlasAsPdfDir" ), dir, QgsSettings::App );
setLastExportPath( dir );
// test directory (if it exists and is writable)
if ( !QDir( dir ).exists() || !QFileInfo( dir ).isWritable() )
@ -2856,7 +2853,7 @@ void QgsLayoutDesignerDialog::exportAtlasToPdf()
progressDialog->setWindowTitle( tr( "Exporting Atlas" ) );
connect( feedback.get(), &QgsFeedback::progressChanged, this, [ & ]( double progress )
{
progressDialog->setValue( progress );
progressDialog->setValue( static_cast< int >( progress ) );
progressDialog->setLabelText( feedback->property( "progress" ).toString() ) ;
#ifdef Q_OS_LINUX
@ -2950,7 +2947,6 @@ void QgsLayoutDesignerDialog::exportAtlasToPdf()
void QgsLayoutDesignerDialog::exportReportToRaster()
{
QgsSettings s;
QString outputFileName = QgsFileUtils::stringToSafeFilename( mMasterLayout->name() );
QPair<QString, QString> fileNExt = QgsGuiUtils::getSaveAsImageName( this, tr( "Save Report As" ), outputFileName );
@ -2961,6 +2957,8 @@ void QgsLayoutDesignerDialog::exportReportToRaster()
return;
}
setLastExportPath( fileNExt.first );
#ifdef Q_OS_MAC
QgisApp::instance()->activateWindow();
this->raise();
@ -3051,10 +3049,8 @@ void QgsLayoutDesignerDialog::exportReportToSvg()
{
showSvgExportWarning();
QgsSettings settings;
QString lastUsedFile = settings.value( QStringLiteral( "lastSaveAsSvgFile" ), QStringLiteral( "qgis.svg" ), QgsSettings::App ).toString();
QFileInfo file( lastUsedFile );
QString outputFileName = file.path() + '/' + QgsFileUtils::stringToSafeFilename( mMasterLayout->name() ) + QStringLiteral( ".svg" );
const QString defaultPath = defaultExportPath();
QString outputFileName = defaultPath + '/' + QgsFileUtils::stringToSafeFilename( mMasterLayout->name() ) + QStringLiteral( ".svg" );
outputFileName = QFileDialog::getSaveFileName(
this,
@ -3076,7 +3072,7 @@ void QgsLayoutDesignerDialog::exportReportToSvg()
this->raise();
#endif
bool prevSettingLabelsAsOutlines = mMasterLayout->layoutProject()->readBoolEntry( QStringLiteral( "PAL" ), QStringLiteral( "/DrawOutlineLabels" ), true );
settings.setValue( QStringLiteral( "lastSaveAsSvgFile" ), outputFileName, QgsSettings::App );
setLastExportPath( outputFileName );
QgsLayoutExporter::SvgExportSettings svgSettings;
bool exportAsText = false;
@ -3179,12 +3175,9 @@ void QgsLayoutDesignerDialog::exportReportToSvg()
void QgsLayoutDesignerDialog::exportReportToPdf()
{
QgsSettings settings;
const QString defaultPath = defaultExportPath();
QString lastUsedFile = settings.value( QStringLiteral( "lastSaveAsPdfFile" ), QStringLiteral( "qgis.pdf" ), QgsSettings::App ).toString();
QFileInfo file( lastUsedFile );
QString outputFileName = file.path() + '/' + QgsFileUtils::stringToSafeFilename( mMasterLayout->name() ) + QStringLiteral( ".pdf" );
QString outputFileName = defaultPath + '/' + QgsFileUtils::stringToSafeFilename( mMasterLayout->name() ) + QStringLiteral( ".pdf" );
#ifdef Q_OS_MAC
QgisApp::instance()->activateWindow();
@ -3205,7 +3198,7 @@ void QgsLayoutDesignerDialog::exportReportToPdf()
{
outputFileName += QLatin1String( ".pdf" );
}
settings.setValue( QStringLiteral( "lastSaveAsPdfFile" ), outputFileName, QgsSettings::App );
setLastExportPath( outputFileName );
mView->setPaintingEnabled( false );
QgsTemporaryCursorOverride cursorOverride( Qt::BusyCursor );
@ -3499,12 +3492,12 @@ void QgsLayoutDesignerDialog::restoreWindowState()
if ( !restoreState( settings.value( QStringLiteral( "LayoutDesigner/state" ), QByteArray::fromRawData( reinterpret_cast< const char * >( defaultLayerDesignerUIstate ), sizeof defaultLayerDesignerUIstate ), QgsSettings::App ).toByteArray() ) )
{
QgsDebugMsg( "restore of layout UI state failed" );
QgsDebugMsg( QStringLiteral( "restore of layout UI state failed" ) );
}
// restore window geometry
if ( !restoreGeometry( settings.value( QStringLiteral( "LayoutDesigner/geometry" ), QgsSettings::App ).toByteArray() ) )
{
QgsDebugMsg( "restore of layout UI geometry failed" );
QgsDebugMsg( QStringLiteral( "restore of layout UI geometry failed" ) );
// default to 80% of screen size, at 10% from top left corner
resize( QDesktopWidget().availableGeometry( this ).size() * 0.8 );
QSize pos = QDesktopWidget().availableGeometry( this ).size() * 0.1;
@ -3727,11 +3720,11 @@ bool QgsLayoutDesignerDialog::showFileSizeWarning()
// Image size
double oneInchInLayoutUnits = mLayout->convertToLayoutUnits( QgsLayoutMeasurement( 1, QgsUnitTypes::LayoutInches ) );
QSizeF maxPageSize = mLayout->pageCollection()->maximumPageSize();
int width = ( int )( mLayout->renderContext().dpi() * maxPageSize.width() / oneInchInLayoutUnits );
int height = ( int )( mLayout->renderContext().dpi() * maxPageSize.height() / oneInchInLayoutUnits );
int width = static_cast< int >( mLayout->renderContext().dpi() * maxPageSize.width() / oneInchInLayoutUnits );
int height = static_cast< int >( mLayout->renderContext().dpi() * maxPageSize.height() / oneInchInLayoutUnits );
int memuse = width * height * 3 / 1000000; // pixmap + image
QgsDebugMsg( QString( "Image %1x%2" ).arg( width ).arg( height ) );
QgsDebugMsg( QString( "memuse = %1" ).arg( memuse ) );
QgsDebugMsg( QStringLiteral( "Image %1x%2" ).arg( width ).arg( height ) );
QgsDebugMsg( QStringLiteral( "memuse = %1" ).arg( memuse ) );
if ( memuse > 400 ) // about 4500x4500
{
@ -4151,6 +4144,36 @@ void QgsLayoutDesignerDialog::updateActionNames( QgsMasterLayoutInterface::Type
}
}
QString QgsLayoutDesignerDialog::defaultExportPath() const
{
// first priority - last export folder saved in project
const QString projectLastExportPath = QgsFileUtils::findClosestExistingPath( QgsProject::instance()->readEntry( QStringLiteral( "Layouts" ), QStringLiteral( "/lastLayoutExportDir" ), QString() ) );
if ( !projectLastExportPath.isEmpty() )
return projectLastExportPath;
// second priority - project home path
const QString projectHome = QgsFileUtils::findClosestExistingPath( QgsProject::instance()->homePath() );
if ( !projectHome.isEmpty() )
return projectHome;
// last priority - app setting last export folder, with homepath as backup
QgsSettings s;
return QgsFileUtils::findClosestExistingPath( s.value( QStringLiteral( "lastLayoutExportDir" ), QDir::homePath(), QgsSettings::App ).toString() );
}
void QgsLayoutDesignerDialog::setLastExportPath( const QString &path ) const
{
QFileInfo fi( path );
QString savePath;
if ( fi.isFile() )
savePath = fi.path();
else
savePath = path;
QgsProject::instance()->writeEntry( QStringLiteral( "Layouts" ), QStringLiteral( "/lastLayoutExportDir" ), savePath );
QgsSettings().setValue( QStringLiteral( "lastLayoutExportDir" ), savePath, QgsSettings::App );
}
void QgsLayoutDesignerDialog::updateWindowTitle()
{
QString title;
@ -4165,7 +4188,7 @@ void QgsLayoutDesignerDialog::updateWindowTitle()
setWindowTitle( title );
}
void QgsLayoutDesignerDialog::selectItems( const QList<QgsLayoutItem *> items )
void QgsLayoutDesignerDialog::selectItems( const QList<QgsLayoutItem *> &items )
{
for ( QGraphicsItem *item : items )
{

View File

@ -59,7 +59,7 @@ class QgsAppLayoutDesignerInterface : public QgsLayoutDesignerInterface
QgsMasterLayoutInterface *masterLayout() override;
QgsLayoutView *view() override;
QgsMessageBar *messageBar() override;
void selectItems( QList< QgsLayoutItem * > items ) override;
void selectItems( const QList< QgsLayoutItem * > &items ) override;
public slots:
@ -132,7 +132,7 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner
/**
* Selects the specified \a items.
*/
void selectItems( QList< QgsLayoutItem * > items );
void selectItems( const QList<QgsLayoutItem *> &items );
/**
* Returns the designer's message bar.
@ -309,7 +309,7 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner
void addPages();
void statusMessageReceived( const QString &message );
void dockVisibilityChanged( bool visible );
void undoRedoOccurredForItems( QSet< QString > itemUuids );
void undoRedoOccurredForItems( const QSet< QString > &itemUuids );
void saveAsTemplate();
void addItemsFromTemplate();
void duplicate();
@ -502,6 +502,9 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner
QString reportTypeString();
void updateActionNames( QgsMasterLayoutInterface::Type type );
QString defaultExportPath() const;
void setLastExportPath( const QString &path ) const;
};
#endif // QGSLAYOUTDESIGNERDIALOG_H

View File

@ -15,6 +15,9 @@
#include "qgsfileutils.h"
#include <QObject>
#include <QRegularExpression>
#include <QFileInfo>
#include <QDir>
#include <QSet>
QString QgsFileUtils::representFileSize( qint64 bytes )
{
@ -29,7 +32,7 @@ QString QgsFileUtils::representFileSize( qint64 bytes )
unit = i.next();
bytes /= 1024.0;
}
return QString( "%1 %2" ).arg( QString::number( bytes ), unit );
return QStringLiteral( "%1 %2" ).arg( QString::number( bytes ), unit );
}
QStringList QgsFileUtils::extensionsFromFilter( const QString &filter )
@ -86,8 +89,41 @@ QString QgsFileUtils::addExtensionFromFilter( const QString &fileName, const QSt
QString QgsFileUtils::stringToSafeFilename( const QString &string )
{
QRegularExpression rx( "[/\\\\\\?%\\*\\:\\|\"<>]" );
QRegularExpression rx( QStringLiteral( "[/\\\\\\?%\\*\\:\\|\"<>]" ) );
QString s = string;
s.replace( rx, QStringLiteral( "_" ) );
return s;
}
QString QgsFileUtils::findClosestExistingPath( const QString &path )
{
if ( path.isEmpty() )
return QString();
QDir currentPath;
QFileInfo fi( path );
if ( fi.isFile() )
currentPath = fi.dir();
else
currentPath = QDir( path );
QSet< QString > visited;
while ( !currentPath.exists() )
{
const QString parentPath = QDir::cleanPath( currentPath.absolutePath() + QStringLiteral( "/.." ) );
if ( visited.contains( parentPath ) )
return QString(); // break circular links
if ( parentPath.isEmpty() || parentPath == '.' )
return QString();
currentPath = QDir( parentPath );
visited << parentPath;
}
const QString res = QDir::cleanPath( currentPath.absolutePath() );
if ( res == QDir::currentPath() )
return QString(); // avoid default to binary folder if a filename alone is specified
return res == '.' ? QString() : res;
}

View File

@ -78,6 +78,14 @@ class CORE_EXPORT QgsFileUtils
* \warning This method strips slashes from the filename, so it is safe to call with file names only, not complete paths.
*/
static QString stringToSafeFilename( const QString &string );
/**
* Returns the top-most existing folder from \a path. E.g. if \a path is "/home/user/projects/2018/P4343"
* and "/home/user/projects" exists but no "2018" subfolder exists, then the function will return "/home/user/projects".
*
* \since QGIS 3.2
*/
static QString findClosestExistingPath( const QString &path );
};
#endif // QGSFILEUTILS_H

View File

@ -612,7 +612,7 @@ void QgsProject::clear()
// basically a debugging tool to dump property list values
void dump_( const QgsProjectPropertyKey &topQgsPropertyKey )
{
QgsDebugMsg( "current properties:" );
QgsDebugMsg( QStringLiteral( "current properties:" ) );
topQgsPropertyKey.dump();
}
@ -660,13 +660,13 @@ void _getProperties( const QDomDocument &doc, QgsProjectPropertyKey &project_pro
if ( scopes.count() < 1 )
{
QgsDebugMsg( "empty ``properties'' XML tag ... bailing" );
QgsDebugMsg( QStringLiteral( "empty ``properties'' XML tag ... bailing" ) );
return;
}
if ( ! project_properties.readXml( propertiesElem ) )
{
QgsDebugMsg( "Project_properties.readXml() failed" );
QgsDebugMsg( QStringLiteral( "Project_properties.readXml() failed" ) );
}
}
@ -683,7 +683,7 @@ static void _getTitle( const QDomDocument &doc, QString &title )
if ( !nl.count() )
{
QgsDebugMsg( "unable to find title element" );
QgsDebugMsg( QStringLiteral( "unable to find title element" ) );
return;
}
@ -691,7 +691,7 @@ static void _getTitle( const QDomDocument &doc, QString &title )
if ( !titleNode.hasChildNodes() ) // if not, then there's no actual text
{
QgsDebugMsg( "unable to find title element" );
QgsDebugMsg( QStringLiteral( "unable to find title element" ) );
return;
}
@ -699,7 +699,7 @@ static void _getTitle( const QDomDocument &doc, QString &title )
if ( !titleTextNode.isText() )
{
QgsDebugMsg( "unable to find title element" );
QgsDebugMsg( QStringLiteral( "unable to find title element" ) );
return;
}
@ -715,7 +715,7 @@ QgsProjectVersion getVersion( const QDomDocument &doc )
if ( !nl.count() )
{
QgsDebugMsg( " unable to find qgis element in project file" );
QgsDebugMsg( QStringLiteral( " unable to find qgis element in project file" ) );
return QgsProjectVersion( 0, 0, 0, QString() );
}
@ -839,7 +839,7 @@ bool QgsProject::addLayer( const QDomElement &layerElem, QList<QDomNode> &broken
if ( !mapLayer )
{
QgsDebugMsg( "Unable to create layer" );
QgsDebugMsg( QStringLiteral( "Unable to create layer" ) );
return false;
}
@ -976,7 +976,7 @@ bool QgsProject::readProjectFile( const QString &filename )
// Shows a warning when an old project file is read.
emit oldProjectVersionWarning( fileVersion.text() );
QgsDebugMsg( "Emitting oldProjectVersionWarning(oldVersion)." );
QgsDebugMsg( QStringLiteral( "Emitting oldProjectVersionWarning(oldVersion)." ) );
projectFile.updateRevision( thisVersion );
}
@ -1124,7 +1124,7 @@ bool QgsProject::readProjectFile( const QString &filename )
// review the integrity of the retrieved map layers
if ( !clean )
{
QgsDebugMsg( "Unable to get map layers from project file." );
QgsDebugMsg( QStringLiteral( "Unable to get map layers from project file." ) );
if ( !brokenNodes.isEmpty() )
{
@ -1551,15 +1551,15 @@ bool QgsProject::writeProjectFile( const QString &filename )
qgisNode.appendChild( titleNode );
QDomElement transactionNode = doc->createElement( QStringLiteral( "autotransaction" ) );
transactionNode.setAttribute( QStringLiteral( "active" ), mAutoTransaction ? "1" : "0" );
transactionNode.setAttribute( QStringLiteral( "active" ), mAutoTransaction ? '1' : '0' );
qgisNode.appendChild( transactionNode );
QDomElement evaluateDefaultValuesNode = doc->createElement( QStringLiteral( "evaluateDefaultValues" ) );
evaluateDefaultValuesNode.setAttribute( QStringLiteral( "active" ), mEvaluateDefaultValues ? "1" : "0" );
evaluateDefaultValuesNode.setAttribute( QStringLiteral( "active" ), mEvaluateDefaultValues ? '1' : '0' );
qgisNode.appendChild( evaluateDefaultValuesNode );
QDomElement trustNode = doc->createElement( QStringLiteral( "trust" ) );
trustNode.setAttribute( QStringLiteral( "active" ), mTrustLayerMetadata ? "1" : "0" );
trustNode.setAttribute( QStringLiteral( "active" ), mTrustLayerMetadata ? '1' : '0' );
qgisNode.appendChild( trustNode );
QDomText titleText = doc->createTextNode( title() ); // XXX why have title TWICE?
@ -1643,7 +1643,7 @@ bool QgsProject::writeProjectFile( const QString &filename )
dump_( mProperties );
QgsDebugMsg( QString( "there are %1 property scopes" ).arg( static_cast<int>( mProperties.count() ) ) );
QgsDebugMsg( QStringLiteral( "there are %1 property scopes" ).arg( static_cast<int>( mProperties.count() ) ) );
if ( !mProperties.isEmpty() ) // only worry about properties if we
// actually have any properties
@ -2691,6 +2691,7 @@ QSet<QgsMapLayer *> QgsProject::requiredLayers() const
void QgsProject::setRequiredLayers( const QSet<QgsMapLayer *> &layers )
{
QStringList layerIds;
layerIds.reserve( layers.count() );
for ( QgsMapLayer *layer : layers )
{
layerIds << layer->id();

View File

@ -76,7 +76,7 @@ class GUI_EXPORT QgsLayoutDesignerInterface: public QObject
/**
* Selects the specified \a items.
*/
virtual void selectItems( QList< QgsLayoutItem * > items ) = 0;
virtual void selectItems( const QList< QgsLayoutItem * > &items ) = 0;
public slots:

View File

@ -14,6 +14,8 @@ __revision__ = '$Format:%H$'
import qgis # NOQA
import tempfile
import os
from qgis.core import QgsFileUtils
from qgis.testing import unittest
@ -61,6 +63,37 @@ class TestQgsFileUtils(unittest.TestCase):
QgsFileUtils.stringToSafeFilename('rendered map_final? rev (12-03-1017)_real/\\?%*:|"<>.tif'),
'rendered map_final_ rev (12-03-1017)_real__________.tif')
def testFindClosestExistingPath(self):
self.assertEqual(QgsFileUtils.findClosestExistingPath(''), '')
self.assertEqual(QgsFileUtils.findClosestExistingPath('.'), '')
self.assertEqual(QgsFileUtils.findClosestExistingPath('just_a_filename'), '')
self.assertEqual(QgsFileUtils.findClosestExistingPath('just_a_filename.txt'), '')
self.assertEqual(QgsFileUtils.findClosestExistingPath('a_very_unlikely_path_to_really_exist/because/no_one_would_have_a_folder_called/MapInfo is the bestest/'), '')
# sorry anyone not on linux!
self.assertEqual(QgsFileUtils.findClosestExistingPath('/usr/youve_been_hacked/by_the_l77t_krew'), '/usr')
base_path = tempfile.mkdtemp()
file = os.path.join(base_path, 'test.csv')
with open(file, 'wt') as f:
f.write('\n')
self.assertEqual(QgsFileUtils.findClosestExistingPath(os.path.join(base_path, 'a file name.bmp')), base_path) # non-existent file
self.assertEqual(QgsFileUtils.findClosestExistingPath(file), base_path) # real file!
self.assertEqual(QgsFileUtils.findClosestExistingPath(os.path.join(base_path, 'non/existent/subfolder')), base_path)
sub_folder1 = os.path.join(base_path, 'subfolder1')
os.mkdir(sub_folder1)
sub_folder2 = os.path.join(sub_folder1, 'subfolder2')
os.mkdir(sub_folder2)
bad_sub_folder = os.path.join(sub_folder2, 'nooo')
self.assertEqual(QgsFileUtils.findClosestExistingPath(bad_sub_folder), sub_folder2)
self.assertEqual(QgsFileUtils.findClosestExistingPath(sub_folder2), sub_folder2)
self.assertEqual(QgsFileUtils.findClosestExistingPath(sub_folder2 + '/.'), sub_folder2)
self.assertEqual(QgsFileUtils.findClosestExistingPath(sub_folder2 + '/..'), sub_folder1)
self.assertEqual(QgsFileUtils.findClosestExistingPath(sub_folder2 + '/../ddddddd'), sub_folder1)
self.assertEqual(QgsFileUtils.findClosestExistingPath(sub_folder2 + '/../subfolder2'), sub_folder2)
self.assertEqual(QgsFileUtils.findClosestExistingPath(sub_folder2 + '/../subfolder2/zxcv/asfdasd'), sub_folder2)
if __name__ == '__main__':
unittest.main()