[layouts] Show an explicit warning when exporting a layout which contains a broken image

This commit is contained in:
Nyall Dawson 2019-01-31 07:57:05 +10:00
parent 4559d9e496
commit e670371fa3
8 changed files with 215 additions and 0 deletions

View File

@ -79,6 +79,8 @@ this value. The path can either be a local path or a remote (http) path.
:return: path for the source image
.. seealso:: :py:func:`setPicturePath`
.. seealso:: :py:func:`evaluatedPath`
%End
double pictureRotation() const;
@ -252,6 +254,24 @@ Returns the current picture mode (image format).
virtual void finalizeRestoreFromXml();
bool isMissingImage() const;
%Docstring
Returns true if the source image is missing and the picture
cannot be rendered.
.. versionadded:: 3.6
%End
QString evaluatedPath() const;
%Docstring
Returns the current evaluated picture path, which includes
the result of data defined path overrides.
.. seealso:: :py:func:`picturePath`
.. versionadded:: 3.6
%End
public slots:
void setPictureRotation( double rotation );

View File

@ -18,6 +18,7 @@
#include "qgsvaliditycheckcontext.h"
#include "qgslayoutitemscalebar.h"
#include "qgslayoutitemmap.h"
#include "qgslayoutitempicture.h"
#include "qgslayout.h"
//
@ -126,3 +127,61 @@ QList<QgsValidityCheckResult> QgsLayoutOverviewValidityCheck::runCheck( const Qg
{
return mResults;
}
//
// QgsLayoutPictureSourceValidityCheck
//
QgsLayoutPictureSourceValidityCheck *QgsLayoutPictureSourceValidityCheck::create() const
{
return new QgsLayoutPictureSourceValidityCheck();
}
QString QgsLayoutPictureSourceValidityCheck::id() const
{
return QStringLiteral( "layout_picture_source_check" );
}
int QgsLayoutPictureSourceValidityCheck::checkType() const
{
return QgsAbstractValidityCheck::TypeLayoutCheck;
}
bool QgsLayoutPictureSourceValidityCheck::prepareCheck( const QgsValidityCheckContext *context, QgsFeedback * )
{
if ( context->type() != QgsValidityCheckContext::TypeLayoutContext )
return false;
const QgsLayoutValidityCheckContext *layoutContext = static_cast< const QgsLayoutValidityCheckContext * >( context );
if ( !layoutContext )
return false;
QList< QgsLayoutItemPicture * > pictureItems;
layoutContext->layout->layoutItems( pictureItems );
for ( QgsLayoutItemPicture *picture : qgis::as_const( pictureItems ) )
{
if ( picture->isMissingImage() )
{
QgsValidityCheckResult res;
res.type = QgsValidityCheckResult::Warning;
res.title = QObject::tr( "Picture source is missing or corrupt" );
const QString name = picture->displayName().toHtmlEscaped();
const QUrl picUrl = QUrl::fromUserInput( picture->evaluatedPath() );
const bool isLocalFile = picUrl.isLocalFile();
res.detailedDescription = QObject::tr( "The source for picture “%1” could not be loaded or is corrupt:<p>%2" ).arg( name,
isLocalFile ? QDir::toNativeSeparators( picture->evaluatedPath() ) : picture->evaluatedPath() );
mResults.append( res );
}
}
return true;
}
QList<QgsValidityCheckResult> QgsLayoutPictureSourceValidityCheck::runCheck( const QgsValidityCheckContext *, QgsFeedback * )
{
return mResults;
}

View File

@ -44,3 +44,18 @@ class APP_EXPORT QgsLayoutOverviewValidityCheck : public QgsAbstractValidityChec
private:
QList<QgsValidityCheckResult> mResults;
};
class APP_EXPORT QgsLayoutPictureSourceValidityCheck : public QgsAbstractValidityCheck
{
public:
QgsLayoutPictureSourceValidityCheck *create() const override;
QString id() const override;
int checkType() const override;
bool prepareCheck( const QgsValidityCheckContext *context, QgsFeedback *feedback ) override;
QList< QgsValidityCheckResult > runCheck( const QgsValidityCheckContext *context, QgsFeedback *feedback ) override;
private:
QList<QgsValidityCheckResult> mResults;
};

View File

@ -1244,6 +1244,7 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh
QgsApplication::validityCheckRegistry()->addCheck( new QgsLayoutScaleBarValidityCheck() );
QgsApplication::validityCheckRegistry()->addCheck( new QgsLayoutOverviewValidityCheck() );
QgsApplication::validityCheckRegistry()->addCheck( new QgsLayoutPictureSourceValidityCheck() );
mSplash->showMessage( tr( "Initializing file filters" ), Qt::AlignHCenter | Qt::AlignBottom );
qApp->processEvents();

View File

@ -485,6 +485,8 @@ void QgsLayoutItemPicture::updateMapRotation()
void QgsLayoutItemPicture::loadPicture( const QString &path )
{
mIsMissingImage = false;
mEvaluatedPath = path;
if ( path.startsWith( QLatin1String( "http" ) ) )
{
//remote location
@ -503,6 +505,7 @@ void QgsLayoutItemPicture::loadPicture( const QString &path )
{
//trying to load an invalid file or bad expression, show cross picture
mMode = FormatSVG;
mIsMissingImage = true;
QString badFile( QStringLiteral( ":/images/composer/missing_image.svg" ) );
mSVG.load( badFile );
if ( mSVG.isValid() )
@ -569,6 +572,16 @@ QSizeF QgsLayoutItemPicture::pictureSize()
}
}
bool QgsLayoutItemPicture::isMissingImage() const
{
return mIsMissingImage;
}
QString QgsLayoutItemPicture::evaluatedPath() const
{
return mEvaluatedPath;
}
void QgsLayoutItemPicture::shapeChanged()
{
if ( mMode == FormatSVG && !mLoadingSvg )

View File

@ -94,6 +94,7 @@ class CORE_EXPORT QgsLayoutItemPicture: public QgsLayoutItem
* this value. The path can either be a local path or a remote (http) path.
* \returns path for the source image
* \see setPicturePath()
* \see evaluatedPath()
*/
QString picturePath() const;
@ -230,6 +231,23 @@ class CORE_EXPORT QgsLayoutItemPicture: public QgsLayoutItem
void finalizeRestoreFromXml() override;
/**
* Returns true if the source image is missing and the picture
* cannot be rendered.
*
* \since QGIS 3.6
*/
bool isMissingImage() const;
/**
* Returns the current evaluated picture path, which includes
* the result of data defined path overrides.
*
* \see picturePath()
* \since QGIS 3.6
*/
QString evaluatedPath() const;
public slots:
/**
@ -318,6 +336,8 @@ class CORE_EXPORT QgsLayoutItemPicture: public QgsLayoutItem
bool mHasExpressionError = false;
bool mLoaded = false;
bool mLoadingSvg = false;
bool mIsMissingImage = false;
QString mEvaluatedPath;
//! Loads an image file into the picture item and redraws the item
void loadPicture( const QString &path );

View File

@ -26,6 +26,7 @@
#include "qgslayout.h"
#include "qgslayoutitemscalebar.h"
#include "qgslayoutitemmap.h"
#include "qgslayoutitempicture.h"
#include "qgsabstractvaliditycheck.h"
#include "qgsvaliditycheckcontext.h"
#include "layout/qgslayoutvaliditychecks.h"
@ -46,6 +47,7 @@ class TestQgsLayoutValidityChecks : public QObject
void testScaleBarValidity();
void testOverviewValidity();
void testPictureValidity();
private:
QString mTestDataDir;
@ -158,6 +160,63 @@ void TestQgsLayoutValidityChecks::testOverviewValidity()
QCOMPARE( res.at( 1 ).type, QgsValidityCheckResult::Warning );
}
void TestQgsLayoutValidityChecks::testPictureValidity()
{
QgsProject p;
QgsLayout l( &p );
QgsLayoutItemPicture *picture = new QgsLayoutItemPicture( &l );
l.addItem( picture );
QgsLayoutValidityCheckContext context( &l );
QgsFeedback f;
// invalid picture source
picture->setPicturePath( QStringLiteral( "blaaaaaaaaaaaaaaaaah" ) );
QgsLayoutPictureSourceValidityCheck check;
QVERIFY( check.prepareCheck( &context, &f ) );
QList< QgsValidityCheckResult > res = check.runCheck( &context, &f );
QCOMPARE( res.size(), 1 );
QCOMPARE( res.at( 0 ).type, QgsValidityCheckResult::Warning );
QgsLayoutPictureSourceValidityCheck check2;
picture->setPicturePath( QString() );
QVERIFY( check2.prepareCheck( &context, &f ) );
res = check2.runCheck( &context, &f );
QCOMPARE( res.size(), 0 );
QgsLayoutPictureSourceValidityCheck check3;
picture->setPicturePath( QStringLiteral( TEST_DATA_DIR ) + "/sample_svg.svg" );
QVERIFY( check3.prepareCheck( &context, &f ) );
res = check3.runCheck( &context, &f );
QCOMPARE( res.size(), 0 );
QgsLayoutItemPicture *picture2 = new QgsLayoutItemPicture( &l );
l.addItem( picture2 );
picture2->dataDefinedProperties().setProperty( QgsLayoutObject::PictureSource, QgsProperty::fromExpression( QStringLiteral( "'d:/bad' || 'robot'" ) ) );
l.refresh();
QgsLayoutPictureSourceValidityCheck check4;
QVERIFY( check4.prepareCheck( &context, &f ) );
res = check4.runCheck( &context, &f );
QCOMPARE( res.size(), 1 );
QCOMPARE( res.at( 0 ).type, QgsValidityCheckResult::Warning );
picture2->dataDefinedProperties().setProperty( QgsLayoutObject::PictureSource, QgsProperty::fromExpression( QStringLiteral( "''" ) ) );
l.refresh();
QgsLayoutPictureSourceValidityCheck check5;
QVERIFY( check5.prepareCheck( &context, &f ) );
res = check5.runCheck( &context, &f );
QCOMPARE( res.size(), 0 );
picture2->dataDefinedProperties().setProperty( QgsLayoutObject::PictureSource, QgsProperty::fromExpression( QStringLiteral( "'%1'" ).arg( QStringLiteral( TEST_DATA_DIR ) + "/sam' || 'ple_svg.svg" ) ) );
l.refresh();
QgsLayoutPictureSourceValidityCheck check6;
QVERIFY( check6.prepareCheck( &context, &f ) );
res = check6.runCheck( &context, &f );
QCOMPARE( res.size(), 0 );
}
QGSTEST_MAIN( TestQgsLayoutValidityChecks )

View File

@ -60,6 +60,7 @@ class TestQgsLayoutPicture : public QObject
void pictureExpression();
void pictureInvalidExpression();
void valid();
private:
@ -422,5 +423,32 @@ void TestQgsLayoutPicture::pictureInvalidExpression()
mPicture->dataDefinedProperties().setProperty( QgsLayoutObject::PictureSource, QgsProperty() );
}
void TestQgsLayoutPicture::valid()
{
QgsProject p;
QgsLayout l( &p );
QgsLayoutItemPicture *picture = new QgsLayoutItemPicture( &l );
l.addItem( picture );
picture->setPicturePath( mPngImage );
QVERIFY( !picture->isMissingImage() );
QCOMPARE( picture->evaluatedPath(), mPngImage );
picture->setPicturePath( QStringLiteral( "bad" ) );
QVERIFY( picture->isMissingImage() );
QCOMPARE( picture->evaluatedPath(), QStringLiteral( "bad" ) );
picture->dataDefinedProperties().setProperty( QgsLayoutObject::PictureSource, QgsProperty::fromExpression( QStringLiteral( "'%1'" ).arg( mSvgImage ) ) );
picture->refreshPicture();
QVERIFY( !picture->isMissingImage() );
QCOMPARE( picture->evaluatedPath(), mSvgImage );
picture->dataDefinedProperties().setProperty( QgsLayoutObject::PictureSource, QgsProperty::fromExpression( QStringLiteral( "'bad'" ) ) );
picture->refreshPicture();
QVERIFY( picture->isMissingImage() );
QCOMPARE( picture->evaluatedPath(), QStringLiteral( "bad" ) );
}
QGSTEST_MAIN( TestQgsLayoutPicture )
#include "testqgslayoutpicture.moc"