Skip to content
Permalink
Browse files

[layouts][FEATURE] Rework picture item UI and behavior

Adds an explicit choice between SVG or raster image sources,
which allows us to clean up the configuration panel for layout pictures
by hiding options which don't apply to a certain picture source. Also permits
us to:
- Reuse the standard svg selector tree widget, which loads images
in a background thread and fixes #17061
- Uses the standard SVG and image selector line edit, which permit
drag and drop of images and expose options to embed images
and link to online sources

Ultimately this is motivated by a desire to allow users to embed
images in layouts and layout templates

Sponsored by SLYR
  • Loading branch information
nyalldawson committed Mar 17, 2020
1 parent 3a32b18 commit 880874bfb68bcc09ffae9abe6650afb8e0a1d110
@@ -62,12 +62,14 @@ The caller takes responsibility for deleting the returned object.
%End


void setPicturePath( const QString &path );
void setPicturePath( const QString &path, Format format = FormatUnknown );
%Docstring
Sets the source ``path`` of the image (may be svg or a raster format). Data defined
picture source may override this value. The path can either be a local path
or a remote (http) path.

Ideally, the ``format`` argument should specify the image format.

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

@@ -249,6 +251,17 @@ Sets the stroke ``width`` (in layout units) used for parametrized SVG files.
Format mode() const;
%Docstring
Returns the current picture mode (image format).

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

void setMode( Format mode );
%Docstring
Sets the current picture ``mode`` (image format).

.. seealso:: :py:func:`mode`

.. versionadded:: 3.14
%End

virtual void finalizeRestoreFromXml();
@@ -35,6 +35,7 @@
#include "qgsbearingutils.h"
#include "qgsmapsettings.h"
#include "qgsreadwritecontext.h"
#include "qgsimagecache.h"

#include <QDomDocument>
#include <QDomElement>
@@ -344,6 +345,7 @@ void QgsLayoutItemPicture::refreshPicture( const QgsExpressionContext *context )
mHasExpressionError = false;
if ( mDataDefinedProperties.isActive( QgsLayoutObject::PictureSource ) )
{
mMode = FormatUnknown;
bool ok = false;
const QgsProperty &sourceProperty = mDataDefinedProperties.property( QgsLayoutObject::PictureSource );
source = sourceProperty.value( *evalContext, source, &ok );
@@ -439,6 +441,45 @@ void QgsLayoutItemPicture::loadLocalPicture( const QString &path )
}
}

void QgsLayoutItemPicture::loadPictureUsingCache( const QString &path )
{
switch ( mMode )
{
case FormatUnknown:
break;

case FormatRaster:
{
bool fitsInCache = false;
mImage = QgsApplication::imageCache()->pathAsImage( path, QSize(), true, 1, fitsInCache, true );
break;
}

case FormatSVG:
{
QgsExpressionContext context = createExpressionContext();
QColor fillColor = mDataDefinedProperties.valueAsColor( QgsLayoutObject::PictureSvgBackgroundColor, context, mSvgFillColor );
QColor strokeColor = mDataDefinedProperties.valueAsColor( QgsLayoutObject::PictureSvgStrokeColor, context, mSvgStrokeColor );
double strokeWidth = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::PictureSvgStrokeWidth, context, mSvgStrokeWidth );
const QByteArray &svgContent = QgsApplication::svgCache()->svgContent( path, rect().width(), fillColor, strokeColor, strokeWidth,
1.0 );
mSVG.load( svgContent );
if ( mSVG.isValid() )
{
mMode = FormatSVG;
QRect viewBox = mSVG.viewBox(); //take width/height ratio from view box instead of default size
mDefaultSvgSize.setWidth( viewBox.width() );
mDefaultSvgSize.setHeight( viewBox.height() );
}
else
{
mMode = FormatUnknown;
}
break;
}
}
}

void QgsLayoutItemPicture::disconnectMap( QgsLayoutItemMap *map )
{
if ( map )
@@ -493,7 +534,7 @@ void QgsLayoutItemPicture::loadPicture( const QVariant &data )
QVariant imageData( data );
mEvaluatedPath = data.toString();

if ( mEvaluatedPath.startsWith( QLatin1String( "base64:" ), Qt::CaseInsensitive ) )
if ( mEvaluatedPath.startsWith( QLatin1String( "base64:" ), Qt::CaseInsensitive ) && mMode == FormatUnknown )
{
QByteArray base64 = mEvaluatedPath.mid( 7 ).toLocal8Bit(); // strip 'base64:' prefix
imageData = QByteArray::fromBase64( base64, QByteArray::OmitTrailingEquals );
@@ -506,16 +547,21 @@ void QgsLayoutItemPicture::loadPicture( const QVariant &data )
mMode = FormatRaster;
}
}
else if ( mEvaluatedPath.startsWith( QLatin1String( "http" ) ) )
else if ( mMode == FormatUnknown && mEvaluatedPath.startsWith( QLatin1String( "http" ) ) )
{
//remote location
//remote location (unsafe way, uses QEventLoop) - for old API/project compatibility only!!
loadRemotePicture( mEvaluatedPath );
}
else
else if ( mMode == FormatUnknown )
{
//local location
//local location - for old API/project compatibility only!!
loadLocalPicture( mEvaluatedPath );
}
else
{
loadPictureUsingCache( mEvaluatedPath );
}

if ( mMode != FormatUnknown ) //make sure we start with a new QImage
{
recalculateSize();
@@ -702,8 +748,9 @@ void QgsLayoutItemPicture::refreshDataDefinedProperty( const QgsLayoutObject::Da
QgsLayoutItem::refreshDataDefinedProperty( property );
}

void QgsLayoutItemPicture::setPicturePath( const QString &path )
void QgsLayoutItemPicture::setPicturePath( const QString &path, Format format )
{
mMode = format;
mSourcePath = path;
refreshPicture();
}
@@ -732,6 +779,7 @@ bool QgsLayoutItemPicture::writePropertiesToElement( QDomElement &elem, QDomDocu
elem.setAttribute( QStringLiteral( "svgFillColor" ), QgsSymbolLayerUtils::encodeColor( mSvgFillColor ) );
elem.setAttribute( QStringLiteral( "svgBorderColor" ), QgsSymbolLayerUtils::encodeColor( mSvgStrokeColor ) );
elem.setAttribute( QStringLiteral( "svgBorderWidth" ), QString::number( mSvgStrokeWidth ) );
elem.setAttribute( QStringLiteral( "mode" ), mMode );

//rotation
elem.setAttribute( QStringLiteral( "pictureRotation" ), QString::number( mPictureRotation ) );
@@ -759,6 +807,7 @@ bool QgsLayoutItemPicture::readPropertiesFromElement( const QDomElement &itemEle
mSvgFillColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "svgFillColor" ), QgsSymbolLayerUtils::encodeColor( QColor( 255, 255, 255 ) ) ) );
mSvgStrokeColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "svgBorderColor" ), QgsSymbolLayerUtils::encodeColor( QColor( 0, 0, 0 ) ) ) );
mSvgStrokeWidth = itemElem.attribute( QStringLiteral( "svgBorderWidth" ), QStringLiteral( "0.2" ) ).toDouble();
mMode = static_cast< Format >( itemElem.attribute( QStringLiteral( "mode" ), QString::number( FormatUnknown ) ).toInt() );

QDomNodeList composerItemList = itemElem.elementsByTagName( QStringLiteral( "ComposerItem" ) );
if ( !composerItemList.isEmpty() )
@@ -854,6 +903,15 @@ void QgsLayoutItemPicture::setSvgStrokeWidth( double width )
refreshPicture();
}

void QgsLayoutItemPicture::setMode( QgsLayoutItemPicture::Format mode )
{
if ( mMode == mode )
return;

mMode = mode;
refreshPicture();
}

void QgsLayoutItemPicture::finalizeRestoreFromXml()
{
if ( !mLayout || mRotationMapUuid.isEmpty() )
@@ -85,9 +85,12 @@ class CORE_EXPORT QgsLayoutItemPicture: public QgsLayoutItem
* Sets the source \a path of the image (may be svg or a raster format). Data defined
* picture source may override this value. The path can either be a local path
* or a remote (http) path.
*
* Ideally, the \a format argument should specify the image format.
*
* \see picturePath()
*/
void setPicturePath( const QString &path );
void setPicturePath( const QString &path, Format format = FormatUnknown );

/**
* Returns the path of the source image. Data defined picture source may override
@@ -226,9 +229,17 @@ class CORE_EXPORT QgsLayoutItemPicture: public QgsLayoutItem

/**
* Returns the current picture mode (image format).
* \see setMode()
*/
Format mode() const { return mMode; }

/**
* Sets the current picture \a mode (image format).
* \see mode()
* \since QGIS 3.14
*/
void setMode( Format mode );

void finalizeRestoreFromXml() override;

/**
@@ -358,6 +369,8 @@ class CORE_EXPORT QgsLayoutItemPicture: public QgsLayoutItem
*/
void loadLocalPicture( const QString &path );

void loadPictureUsingCache( const QString &path );

void disconnectMap( QgsLayoutItemMap *map );

private slots:

0 comments on commit 880874b

Please sign in to comment.
You can’t perform that action at this time.