Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[FEATURE] supports dynamic SVGs in layouts
also fix a UX bug where you couldn't switch between raster and SVG radios if the data defined property was active (unreported in tracker)
  • Loading branch information
3nids committed May 11, 2021
1 parent c1b8d4a commit 6817d94
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 380 deletions.
14 changes: 14 additions & 0 deletions python/core/auto_generated/layout/qgslayoutitempicture.sip.in
Expand Up @@ -283,6 +283,20 @@ the result of data defined path overrides.
.. seealso:: :py:func:`picturePath`

.. versionadded:: 3.6
%End

QMap<QString, QgsProperty> svgDynamicParameters() const;
%Docstring
Returns the SVG dynamic parameters

.. versionadded:: 3.20
%End

void setSvgDynamicParameters( const QMap<QString, QgsProperty> &parameters );
%Docstring
Sets the SVG dynamic parameters

.. versionadded:: 3.20
%End

public slots:
Expand Down
Expand Up @@ -149,6 +149,7 @@ the widget was updated.
Returns the atlas for the layout (if available)
%End


};


Expand Down
37 changes: 33 additions & 4 deletions src/core/layout/qgslayoutitempicture.cpp
Expand Up @@ -413,8 +413,10 @@ void QgsLayoutItemPicture::loadLocalPicture( const QString &path )
QColor fillColor = mDataDefinedProperties.valueAsColor( QgsLayoutObject::PictureSvgBackgroundColor, context, mSvgFillColor );
QColor strokeColor = mDataDefinedProperties.valueAsColor( QgsLayoutObject::PictureSvgStrokeColor, context, mSvgStrokeColor );
double strokeWidth = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::PictureSvgStrokeWidth, context, mSvgStrokeWidth );
QgsStringMap evaluatedParameters = QgsSymbolLayerUtils::evaluatePropertiesMap( svgDynamicParameters(), context );

const QByteArray &svgContent = QgsApplication::svgCache()->svgContent( path, rect().width(), fillColor, strokeColor, strokeWidth,
1.0 );
1.0, 0, false, evaluatedParameters );
mSVG.load( svgContent );
if ( mSVG.isValid() )
{
Expand Down Expand Up @@ -473,11 +475,12 @@ void QgsLayoutItemPicture::loadPictureUsingCache( const QString &path )
QColor fillColor = mDataDefinedProperties.valueAsColor( QgsLayoutObject::PictureSvgBackgroundColor, context, mSvgFillColor );
QColor strokeColor = mDataDefinedProperties.valueAsColor( QgsLayoutObject::PictureSvgStrokeColor, context, mSvgStrokeColor );
double strokeWidth = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::PictureSvgStrokeWidth, context, mSvgStrokeWidth );
// TODO parameters (handle this in the gui part)
QMap<QString, QString> parameters;

QgsStringMap evaluatedParameters = QgsSymbolLayerUtils::evaluatePropertiesMap( svgDynamicParameters(), context );

bool isMissingImage = false;
const QByteArray &svgContent = QgsApplication::svgCache()->svgContent( path, rect().width(), fillColor, strokeColor, strokeWidth,
1.0, 0, false, parameters, &isMissingImage );
1.0, 0, false, evaluatedParameters, &isMissingImage );
mSVG.load( svgContent );
if ( mSVG.isValid() && !isMissingImage )
{
Expand Down Expand Up @@ -620,6 +623,32 @@ QString QgsLayoutItemPicture::evaluatedPath() const
return mEvaluatedPath;
}

QMap<QString, QgsProperty> QgsLayoutItemPicture::svgDynamicParameters() const
{
const QVariantMap parameters = mCustomProperties.value( QStringLiteral( "svg-dynamic-parameters" ), QVariantMap() ).toMap();

QMap<QString, QgsProperty> parametersProperties;
QVariantMap::const_iterator it = parameters.constBegin();
for ( ; it != parameters.constEnd(); ++it )
{
QgsProperty property;
if ( property.loadVariant( it.value() ) )
parametersProperties.insert( it.key(), property );
}

return parametersProperties;
}

void QgsLayoutItemPicture::setSvgDynamicParameters( const QMap<QString, QgsProperty> &parameters )
{
QVariantMap variantParameters;
QMap<QString, QgsProperty>::const_iterator it = parameters.constBegin();
for ( ; it != parameters.constEnd(); ++it )
variantParameters.insert( it.key(), it.value().toVariant() );

mCustomProperties.setValue( QStringLiteral( "svg-dynamic-parameters" ), variantParameters );
}

void QgsLayoutItemPicture::shapeChanged()
{
if ( mMode == FormatSVG && !mLoadingSvg )
Expand Down
12 changes: 12 additions & 0 deletions src/core/layout/qgslayoutitempicture.h
Expand Up @@ -260,6 +260,18 @@ class CORE_EXPORT QgsLayoutItemPicture: public QgsLayoutItem
*/
QString evaluatedPath() const;

/**
* Returns the SVG dynamic parameters
* \since QGIS 3.20
*/
QMap<QString, QgsProperty> svgDynamicParameters() const;

/**
* Sets the SVG dynamic parameters
* \since QGIS 3.20
*/
void setSvgDynamicParameters( const QMap<QString, QgsProperty> &parameters );

public slots:

/**
Expand Down
5 changes: 2 additions & 3 deletions src/gui/layout/qgslayoutitemwidget.h
Expand Up @@ -200,11 +200,10 @@ class GUI_EXPORT QgsLayoutItemBaseWidget: public QgsPanelWidget
//! Returns the atlas for the layout (if available)
QgsLayoutAtlas *layoutAtlas() const;

private:
QgsLayoutObject *mObject = nullptr;

private:
QgsLayoutConfigObject *mConfigObject = nullptr;

QgsLayoutObject *mObject = nullptr;
};


Expand Down
167 changes: 43 additions & 124 deletions src/gui/layout/qgslayoutpicturewidget.cpp
Expand Up @@ -24,6 +24,7 @@
#include "qgssvgcache.h"
#include "qgssettings.h"
#include "qgssvgselectorwidget.h"
#include "qgsfilecontentsourcelineedit.h"

#include <QDoubleValidator>
#include <QFileDialog>
Expand All @@ -40,6 +41,11 @@ QgsLayoutPictureWidget::QgsLayoutPictureWidget( QgsLayoutItemPicture *picture )
{
setupUi( this );

mSvgSelectorWidget->setAllowParameters( true );
mSvgSelectorWidget->sourceLineEdit()->setPropertyOverrideToolButtonVisible( true );
mSvgSelectorWidget->sourceLineEdit()->setLastPathSettingsKey( QStringLiteral( "/UI/lastSVGMarkerDir" ) );
mSvgSelectorWidget->initParametersModel( mObject );

mResizeModeComboBox->addItem( tr( "Zoom" ), QgsLayoutItemPicture::Zoom );
mResizeModeComboBox->addItem( tr( "Stretch" ), QgsLayoutItemPicture::Stretch );
mResizeModeComboBox->addItem( tr( "Clip" ), QgsLayoutItemPicture::Clip );
Expand All @@ -65,12 +71,12 @@ QgsLayoutPictureWidget::QgsLayoutPictureWidget( QgsLayoutItemPicture *picture )
connect( mStrokeWidthSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutPictureWidget::mStrokeWidthSpinBox_valueChanged );
connect( mPictureRotationOffsetSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsLayoutPictureWidget::mPictureRotationOffsetSpinBox_valueChanged );
connect( mNorthTypeComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsLayoutPictureWidget::mNorthTypeComboBox_currentIndexChanged );
connect( mSvgSelectorWidget->sourceLineEdit(), &QgsSvgSourceLineEdit::sourceChanged, this, &QgsLayoutPictureWidget::sourceChanged );
connect( mSvgSelectorWidget, &QgsSvgSelectorWidget::svgParametersChanged, this, &QgsLayoutPictureWidget::setSvgParameters );
connect( mRadioSVG, &QRadioButton::toggled, this, &QgsLayoutPictureWidget::modeChanged );
connect( mRadioRaster, &QRadioButton::toggled, this, &QgsLayoutPictureWidget::modeChanged );
connect( mSvgSourceLineEdit, &QgsSvgSourceLineEdit::sourceChanged, this, &QgsLayoutPictureWidget::svgSourceChanged );
connect( mImageSourceLineEdit, &QgsImageSourceLineEdit::sourceChanged, this, &QgsLayoutPictureWidget::rasterSourceChanged );

mSvgSourceLineEdit->setLastPathSettingsKey( QStringLiteral( "/UI/lastComposerPictureDir" ) );
mSvgSelectorWidget->sourceLineEdit()->setLastPathSettingsKey( QStringLiteral( "/UI/lastComposerPictureDir" ) );

setPanelTitle( tr( "Picture Properties" ) );

Expand All @@ -91,16 +97,6 @@ QgsLayoutPictureWidget::QgsLayoutPictureWidget( QgsLayoutItemPicture *picture )
mPictureRotationOffsetSpinBox->setClearValue( 0.0 );
mPictureRotationSpinBox->setClearValue( 0.0 );

viewGroups->setHeaderHidden( true );
mIconSize = std::max( 30, static_cast< int >( std::round( Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * 4 ) ) );
viewImages->setGridSize( QSize( mIconSize * 1.2, mIconSize * 1.2 ) );
viewImages->setUniformItemSizes( false );
populateList();

connect( viewImages->selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsLayoutPictureWidget::setSvgName );
connect( viewGroups->selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsLayoutPictureWidget::populateIcons );


//add widget for general composer item properties
mItemPropertiesWidget = new QgsLayoutItemPropertiesWidget( this, picture );
mainLayout->addWidget( mItemPropertiesWidget );
Expand All @@ -114,19 +110,25 @@ QgsLayoutPictureWidget::QgsLayoutPictureWidget( QgsLayoutItemPicture *picture )

setGuiElementValues();

switch ( mPicture->mode() )
{
case QgsLayoutItemPicture::FormatSVG:
case QgsLayoutItemPicture::FormatUnknown:
mRadioSVG->setChecked( true );
break;
case QgsLayoutItemPicture::FormatRaster:
mRadioRaster->setChecked( true );
break;
}

connect( mPicture, &QgsLayoutObject::changed, this, &QgsLayoutPictureWidget::setGuiElementValues );
connect( mPicture, &QgsLayoutItemPicture::pictureRotationChanged, this, &QgsLayoutPictureWidget::setPicRotationSpinValue );

//connections for data defined buttons
mSourceDDBtn->registerEnabledWidget( mImageSourceLineEdit, false );
mSourceDDBtn->registerEnabledWidget( mSvgSourceLineEdit, false );

registerDataDefinedButton( mSourceDDBtn, QgsLayoutObject::PictureSource );
registerDataDefinedButton( mSvgSelectorWidget->propertyOverrideToolButton(), QgsLayoutObject::PictureSource );
registerDataDefinedButton( mFillColorDDBtn, QgsLayoutObject::PictureSvgBackgroundColor );
registerDataDefinedButton( mStrokeColorDDBtn, QgsLayoutObject::PictureSvgStrokeColor );
registerDataDefinedButton( mStrokeWidthDDBtn, QgsLayoutObject::PictureSvgStrokeWidth );

updatePictureTypeWidgets();
}

void QgsLayoutPictureWidget::setMasterLayout( QgsMasterLayoutInterface *masterLayout )
Expand Down Expand Up @@ -327,33 +329,7 @@ void QgsLayoutPictureWidget::setGuiElementValues()
mAnchorPointComboBox->setEnabled( false );
}

whileBlocking( mRadioSVG )->setChecked( mPicture->mode() == QgsLayoutItemPicture::FormatSVG );
whileBlocking( mRadioRaster )->setChecked( mPicture->mode() == QgsLayoutItemPicture::FormatRaster );
updatePictureTypeWidgets();

if ( mRadioSVG->isChecked() )
{
whileBlocking( mSvgSourceLineEdit )->setSource( mPicture->picturePath() );

mBlockSvgModelChanges++;
QAbstractItemModel *m = viewImages->model();
QItemSelectionModel *selModel = viewImages->selectionModel();
for ( int i = 0; i < m->rowCount(); i++ )
{
QModelIndex idx( m->index( i, 0 ) );
if ( m->data( idx ).toString() == mPicture->picturePath() )
{
selModel->select( idx, QItemSelectionModel::SelectCurrent );
selModel->setCurrentIndex( idx, QItemSelectionModel::SelectCurrent );
break;
}
}
mBlockSvgModelChanges--;
}
else if ( mRadioRaster->isChecked() )
{
whileBlocking( mImageSourceLineEdit )->setSource( mPicture->picturePath() );
}
mSvgSelectorWidget->setSvgParameters( mPicture->svgDynamicParameters() );

updateSvgParamGui( false );
mFillColorButton->setColor( mPicture->svgFillColor() );
Expand Down Expand Up @@ -382,7 +358,7 @@ void QgsLayoutPictureWidget::updateSvgParamGui( bool resetValues )

QString picturePath = mPicture->picturePath();

//activate gui for svg parameters only if supported by the svg file
//activate gui for svg parameters only if supported by the svg file/ do nothing
bool hasFillParam, hasFillOpacityParam, hasStrokeParam, hasStrokeWidthParam, hasStrokeOpacityParam;
QColor defaultFill, defaultStroke;
double defaultStrokeWidth, defaultFillOpacity, defaultStrokeOpacity;
Expand Down Expand Up @@ -466,78 +442,28 @@ void QgsLayoutPictureWidget::mNorthTypeComboBox_currentIndexChanged( int index )
mPicture->update();
}

void QgsLayoutPictureWidget::modeChanged()
void QgsLayoutPictureWidget::modeChanged( bool checked )
{
const QgsLayoutItemPicture::Format newFormat = mRadioSVG->isChecked() ? QgsLayoutItemPicture::FormatSVG : QgsLayoutItemPicture::FormatRaster;
if ( mPicture && mPicture->mode() != newFormat )
{
whileBlocking( mSvgSourceLineEdit )->setSource( QString() );
whileBlocking( mImageSourceLineEdit )->setSource( QString() );
mPicture->beginCommand( tr( "Change Picture Type" ) );
mPicture->setPicturePath( QString(), newFormat );
mPicture->endCommand();
}
updatePictureTypeWidgets();
}
if ( !checked )
return;

void QgsLayoutPictureWidget::updatePictureTypeWidgets()
{
mRasterFrame->setVisible( mRadioRaster->isChecked() );
mSVGFrame->setVisible( mRadioSVG->isChecked() );
mSVGParamsGroupBox->setVisible( mRadioSVG->isChecked() );
bool svg = mRadioSVG->isChecked();
const QgsLayoutItemPicture::Format newFormat = svg ? QgsLayoutItemPicture::FormatSVG : QgsLayoutItemPicture::FormatRaster;

// need to move the data defined button to the appropriate frame -- we can't have two buttons linked to the one property!
if ( mRadioSVG->isChecked() )
mSvgDDBtnFrame->layout()->addWidget( mSourceDDBtn );
else
mRasterDDBtnFrame->layout()->addWidget( mSourceDDBtn );
}
mSvgSelectorWidget->setAllowAnyImage( svg );
mSvgSelectorWidget->setBrowserVisible( svg );
mSvgSelectorWidget->setAllowParameters( svg );
mSVGParamsGroupBox->setVisible( svg );

void QgsLayoutPictureWidget::populateList()
{
QAbstractItemModel *oldModel = viewGroups->model();
QgsSvgSelectorGroupsModel *g = new QgsSvgSelectorGroupsModel( viewGroups );
viewGroups->setModel( g );
delete oldModel;

// Set the tree expanded at the first level
int rows = g->rowCount( g->indexFromItem( g->invisibleRootItem() ) );
for ( int i = 0; i < rows; i++ )
if ( mPicture && mPicture->mode() != newFormat )
{
viewGroups->setExpanded( g->indexFromItem( g->item( i ) ), true );
mPicture->beginCommand( tr( "Change Picture Type" ) );
mPicture->setMode( newFormat );
mPicture->endCommand();
}

// Initially load the icons in the List view without any grouping
oldModel = viewImages->model();
QgsSvgSelectorListModel *m = new QgsSvgSelectorListModel( viewImages, mIconSize );
viewImages->setModel( m );

delete oldModel;
}

void QgsLayoutPictureWidget::populateIcons( const QModelIndex &idx )
{
QString path = idx.data( Qt::UserRole + 1 ).toString();

QAbstractItemModel *oldModel = viewImages->model();
QgsSvgSelectorListModel *m = new QgsSvgSelectorListModel( viewImages, path, mIconSize );
viewImages->setModel( m );
delete oldModel;

connect( viewImages->selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsLayoutPictureWidget::setSvgName );
}

void QgsLayoutPictureWidget::setSvgName( const QModelIndex &idx )
{
if ( mBlockSvgModelChanges )
return;

QString name = idx.data( Qt::UserRole ).toString();
whileBlocking( mSvgSourceLineEdit )->setSource( name );
svgSourceChanged( name );
}

void QgsLayoutPictureWidget::svgSourceChanged( const QString &source )
void QgsLayoutPictureWidget::sourceChanged( const QString &source )
{
if ( mPicture )
{
Expand All @@ -549,26 +475,19 @@ void QgsLayoutPictureWidget::svgSourceChanged( const QString &source )
}
}

void QgsLayoutPictureWidget::rasterSourceChanged( const QString &source )
void QgsLayoutPictureWidget::setSvgParameters( const QMap<QString, QgsProperty> &parameters )
{
if ( mPicture )
{
mPicture->beginCommand( tr( "Change Picture" ) );
mPicture->setPicturePath( source, QgsLayoutItemPicture::FormatRaster );
mPicture->update();
mPicture->endCommand();
}
mPicture->beginCommand( tr( "Set SVG parameters" ) );
mPicture->setSvgDynamicParameters( parameters );
mPicture->update();
mPicture->endCommand();
}

void QgsLayoutPictureWidget::populateDataDefinedButtons()
{
updateDataDefinedButton( mSourceDDBtn );
updateDataDefinedButton( mSvgSelectorWidget->propertyOverrideToolButton() );
updateDataDefinedButton( mFillColorDDBtn );
updateDataDefinedButton( mStrokeColorDDBtn );
updateDataDefinedButton( mStrokeWidthDDBtn );

//initial state of controls - disable related controls when dd buttons are active
mImageSourceLineEdit->setEnabled( !mSourceDDBtn->isActive() );
mSvgSourceLineEdit->setEnabled( !mSourceDDBtn->isActive() );
}

12 changes: 4 additions & 8 deletions src/gui/layout/qgslayoutpicturewidget.h
Expand Up @@ -70,14 +70,10 @@ class GUI_EXPORT QgsLayoutPictureWidget: public QgsLayoutItemBaseWidget, private
void mStrokeWidthSpinBox_valueChanged( double d );
void mPictureRotationOffsetSpinBox_valueChanged( double d );
void mNorthTypeComboBox_currentIndexChanged( int index );
void modeChanged();
void updatePictureTypeWidgets();

void populateList();
void populateIcons( const QModelIndex &idx );
void setSvgName( const QModelIndex &idx );
void svgSourceChanged( const QString &source );
void rasterSourceChanged( const QString &source );

void sourceChanged( const QString &source );
void setSvgParameters( const QMap<QString, QgsProperty> &parameters );
void modeChanged( bool checked );
private:
QPointer< QgsLayoutItemPicture > mPicture;
QgsLayoutItemPropertiesWidget *mItemPropertiesWidget = nullptr;
Expand Down

0 comments on commit 6817d94

Please sign in to comment.