From ec9ba9c2a2fab4969d0927b4c509ce7573ea35a3 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 17 Jan 2017 14:32:30 +1000 Subject: [PATCH] [FEATURE][composer] Data defined legend titles and column count (fix #11913) --- python/core/composer/qgscomposerlegend.sip | 4 + python/core/composer/qgscomposerobject.sip | 3 + src/app/composer/qgscomposerlegendwidget.cpp | 7 ++ src/core/composer/qgscomposerlegend.cpp | 53 ++++++-- src/core/composer/qgscomposerlegend.h | 12 ++ src/core/composer/qgscomposerobject.cpp | 2 + src/core/composer/qgscomposerobject.h | 3 + .../composer/qgscomposerlegendwidgetbase.ui | 119 +++++++++++------- tests/src/python/test_qgscomposerlegend.py | 40 +++++- 9 files changed, 187 insertions(+), 56 deletions(-) diff --git a/python/core/composer/qgscomposerlegend.sip b/python/core/composer/qgscomposerlegend.sip index e21efd8a26a..a20977ea5ee 100644 --- a/python/core/composer/qgscomposerlegend.sip +++ b/python/core/composer/qgscomposerlegend.sip @@ -242,9 +242,13 @@ class QgsComposerLegend : QgsComposerItem //Overridden to show legend title virtual QString displayName() const; + const QgsLegendSettings& legendSettings() const; + public slots: /** Data changed*/ void synchronizeWithModel(); /** Sets mCompositionMap to 0 if the map is deleted*/ void invalidateCurrentMap(); + virtual void refreshDataDefinedProperty( const QgsComposerObject::DataDefinedProperty property = QgsComposerObject::AllProperties, const QgsExpressionContext* context = nullptr ); + }; diff --git a/python/core/composer/qgscomposerobject.sip b/python/core/composer/qgscomposerobject.sip index b1622ee6854..c01703de58f 100644 --- a/python/core/composer/qgscomposerobject.sip +++ b/python/core/composer/qgscomposerobject.sip @@ -51,6 +51,9 @@ class QgsComposerObject : QObject, QgsExpressionContextGenerator PictureSvgOutlineWidth, //!< SVG outline width //html item SourceUrl /*!< html source url */ + //legend item + LegendTitle, //!< Legend title + LegendColumnCount, //!< Legend column count }; /** Specifies whether the value returned by a function should be the original, user diff --git a/src/app/composer/qgscomposerlegendwidget.cpp b/src/app/composer/qgscomposerlegendwidget.cpp index 8412e7b205e..329eac3f66b 100644 --- a/src/app/composer/qgscomposerlegendwidget.cpp +++ b/src/app/composer/qgscomposerlegendwidget.cpp @@ -100,6 +100,11 @@ QgsComposerLegendWidget::QgsComposerLegendWidget( QgsComposerLegend* legend ) connect( &legend->composition()->atlasComposition(), SIGNAL( coverageLayerChanged( QgsVectorLayer* ) ), this, SLOT( updateFilterLegendByAtlasButton() ) ); } + registerDataDefinedButton( mLegendTitleDDBtn, QgsComposerObject::LegendTitle, + QgsDataDefinedButtonV2::AnyType, QgsDataDefinedButtonV2::anyStringDesc() ); + registerDataDefinedButton( mColumnsDDBtn, QgsComposerObject::LegendColumnCount, + QgsDataDefinedButtonV2::AnyType, QgsDataDefinedButtonV2::intPosOneDesc() ); + setGuiElements(); connect( mItemTreeView->selectionModel(), SIGNAL( currentChanged( const QModelIndex &, const QModelIndex & ) ), @@ -160,6 +165,8 @@ void QgsComposerLegendWidget::setGuiElements() blockAllSignals( false ); on_mCheckBoxAutoUpdate_stateChanged( mLegend->autoUpdateModel() ? Qt::Checked : Qt::Unchecked ); + updateDataDefinedButton( mLegendTitleDDBtn ); + updateDataDefinedButton( mColumnsDDBtn ); } void QgsComposerLegendWidget::on_mWrapCharLineEdit_textChanged( const QString &text ) diff --git a/src/core/composer/qgscomposerlegend.cpp b/src/core/composer/qgscomposerlegend.cpp index 93dfde19a3a..1c4692b805e 100644 --- a/src/core/composer/qgscomposerlegend.cpp +++ b/src/core/composer/qgscomposerlegend.cpp @@ -265,6 +265,7 @@ void QgsComposerLegend::setLegendFilterByMapEnabled( bool enabled ) void QgsComposerLegend::setTitle( const QString& t ) { + mTitle = t; mSettings.setTitle( t ); if ( mComposition && id().isEmpty() ) @@ -273,7 +274,7 @@ void QgsComposerLegend::setTitle( const QString& t ) mComposition->itemsModel()->updateItemDisplayName( this ); } } -QString QgsComposerLegend::title() const { return mSettings.title(); } +QString QgsComposerLegend::title() const { return mTitle; } Qt::AlignmentFlag QgsComposerLegend::titleAlignment() const { return mSettings.titleAlignment(); } void QgsComposerLegend::setTitleAlignment( Qt::AlignmentFlag alignment ) { mSettings.setTitleAlignment( alignment ); } @@ -315,8 +316,8 @@ void QgsComposerLegend::setWmsLegendHeight( double h ) { mSettings.setWmsLegendS void QgsComposerLegend::setWrapChar( const QString& t ) { mSettings.setWrapChar( t ); } QString QgsComposerLegend::wrapChar() const {return mSettings.wrapChar(); } -int QgsComposerLegend::columnCount() const { return mSettings.columnCount(); } -void QgsComposerLegend::setColumnCount( int c ) { mSettings.setColumnCount( c ); } +int QgsComposerLegend::columnCount() const { return mColumnCount; } +void QgsComposerLegend::setColumnCount( int c ) { mColumnCount = c; mSettings.setColumnCount( c ); } bool QgsComposerLegend::splitLayer() const { return mSettings.splitLayer(); } void QgsComposerLegend::setSplitLayer( bool s ) { mSettings.setSplitLayer( s ); } @@ -362,9 +363,9 @@ bool QgsComposerLegend::writeXml( QDomElement& elem, QDomDocument & doc ) const elem.appendChild( composerLegendElem ); //write general properties - composerLegendElem.setAttribute( QStringLiteral( "title" ), mSettings.title() ); + composerLegendElem.setAttribute( QStringLiteral( "title" ), mTitle ); composerLegendElem.setAttribute( QStringLiteral( "titleAlignment" ), QString::number( static_cast< int >( mSettings.titleAlignment() ) ) ); - composerLegendElem.setAttribute( QStringLiteral( "columnCount" ), QString::number( mSettings.columnCount() ) ); + composerLegendElem.setAttribute( QStringLiteral( "columnCount" ), QString::number( mColumnCount ) ); composerLegendElem.setAttribute( QStringLiteral( "splitLayer" ), QString::number( mSettings.splitLayer() ) ); composerLegendElem.setAttribute( QStringLiteral( "equalColumnWidth" ), QString::number( mSettings.equalColumnWidth() ) ); @@ -462,14 +463,16 @@ bool QgsComposerLegend::readXml( const QDomElement& itemElem, const QDomDocument } //read general properties - mSettings.setTitle( itemElem.attribute( QStringLiteral( "title" ) ) ); + mTitle = itemElem.attribute( QStringLiteral( "title" ) ); + mSettings.setTitle( mTitle ); if ( !itemElem.attribute( QStringLiteral( "titleAlignment" ) ).isEmpty() ) { mSettings.setTitleAlignment( static_cast< Qt::AlignmentFlag >( itemElem.attribute( QStringLiteral( "titleAlignment" ) ).toInt() ) ); } int colCount = itemElem.attribute( QStringLiteral( "columnCount" ), QStringLiteral( "1" ) ).toInt(); if ( colCount < 1 ) colCount = 1; - mSettings.setColumnCount( colCount ); + mColumnCount = colCount; + mSettings.setColumnCount( mColumnCount ); mSettings.setSplitLayer( itemElem.attribute( QStringLiteral( "splitLayer" ), QStringLiteral( "0" ) ).toInt() == 1 ); mSettings.setEqualColumnWidth( itemElem.attribute( QStringLiteral( "equalColumnWidth" ), QStringLiteral( "0" ) ).toInt() == 1 ); @@ -642,6 +645,42 @@ void QgsComposerLegend::invalidateCurrentMap() setComposerMap( nullptr ); } +void QgsComposerLegend::refreshDataDefinedProperty( const QgsComposerObject::DataDefinedProperty property, const QgsExpressionContext* context ) +{ + QgsExpressionContext scopedContext = createExpressionContext(); + const QgsExpressionContext* evalContext = context ? context : &scopedContext; + + bool forceUpdate = false; + //updates data defined properties and redraws item to match + if ( property == QgsComposerObject::LegendTitle || property == QgsComposerObject::AllProperties ) + { + bool ok = false; + QString t = mProperties.valueAsString( QgsComposerObject::LegendTitle, *evalContext, mTitle, &ok ); + if ( ok ) + { + mSettings.setTitle( t ); + forceUpdate = true; + } + } + if ( property == QgsComposerObject::LegendColumnCount || property == QgsComposerObject::AllProperties ) + { + bool ok = false; + int cols = mProperties.valueAsInt( QgsComposerObject::LegendColumnCount, *evalContext, mColumnCount, &ok ); + if ( ok && cols >= 0 ) + { + mSettings.setColumnCount( cols ); + forceUpdate = true; + } + } + if ( forceUpdate ) + { + adjustBoxSize(); + update(); + } + + QgsComposerObject::refreshDataDefinedProperty( property, context ); +} + void QgsComposerLegend::mapLayerStyleOverridesChanged() { if ( !mComposerMap ) diff --git a/src/core/composer/qgscomposerlegend.h b/src/core/composer/qgscomposerlegend.h index 8fb098434a9..14e6783ac8c 100644 --- a/src/core/composer/qgscomposerlegend.h +++ b/src/core/composer/qgscomposerlegend.h @@ -272,12 +272,21 @@ class CORE_EXPORT QgsComposerLegend : public QgsComposerItem //Overridden to show legend title virtual QString displayName() const override; + /** + * Returns the legend's renderer settings object. + * @note added in QGIS 3.0 + */ + const QgsLegendSettings& legendSettings() const { return mSettings; } + public slots: //! Data changed void synchronizeWithModel(); //! Sets mCompositionMap to 0 if the map is deleted void invalidateCurrentMap(); + virtual void refreshDataDefinedProperty( const QgsComposerObject::DataDefinedProperty property = QgsComposerObject::AllProperties, const QgsExpressionContext* context = nullptr ) override; + + private slots: void updateFilterByMap( bool redraw = true ); @@ -301,6 +310,9 @@ class CORE_EXPORT QgsComposerLegend : public QgsComposerItem QgsLegendSettings mSettings; + QString mTitle; + int mColumnCount = 1; + const QgsComposerMap* mComposerMap; bool mLegendFilterByMap; diff --git a/src/core/composer/qgscomposerobject.cpp b/src/core/composer/qgscomposerobject.cpp index 1ffe8ae28a3..7b85dd619cb 100644 --- a/src/core/composer/qgscomposerobject.cpp +++ b/src/core/composer/qgscomposerobject.cpp @@ -61,6 +61,8 @@ const QgsPropertyDefinition QgsComposerObject::sPropertyNameMap { QgsComposerObject::PictureSvgBackgroundColor, "dataDefinedSvgBackgroundColor" }, { QgsComposerObject::PictureSvgOutlineColor, "dataDefinedSvgOutlineColor" }, { QgsComposerObject::PictureSvgOutlineWidth, "dataDefinedSvgOutlineWidth" }, + { QgsComposerObject::LegendTitle, "dataDefinedLegendTitle" }, + { QgsComposerObject::LegendColumnCount, "dataDefinedLegendColumns" }, }; diff --git a/src/core/composer/qgscomposerobject.h b/src/core/composer/qgscomposerobject.h index f87aaa8a490..a4774f5c55e 100644 --- a/src/core/composer/qgscomposerobject.h +++ b/src/core/composer/qgscomposerobject.h @@ -78,6 +78,9 @@ class CORE_EXPORT QgsComposerObject: public QObject, public QgsExpressionContext PictureSvgOutlineWidth, //!< SVG outline width //html item SourceUrl, //!< Html source url + //legend item + LegendTitle, //!< Legend title + LegendColumnCount, //!< Legend column count }; static const QgsPropertyDefinition sPropertyNameMap; diff --git a/src/ui/composer/qgscomposerlegendwidgetbase.ui b/src/ui/composer/qgscomposerlegendwidgetbase.ui index d40061bb318..0a3f8aab921 100644 --- a/src/ui/composer/qgscomposerlegendwidgetbase.ui +++ b/src/ui/composer/qgscomposerlegendwidgetbase.ui @@ -83,13 +83,7 @@ false - - - QFormLayout::AllNonFixedFieldsGrow - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - + @@ -100,8 +94,12 @@ - - + + + + Title alignment + + @@ -110,8 +108,12 @@ - - + + + + Resize to fit contents + + @@ -120,21 +122,17 @@ - - - - true - - + + - - + + - Title alignment + ... - + @@ -153,10 +151,13 @@ - - - - Resize to fit contents + + + + + + + true @@ -550,21 +551,8 @@ true - - - QFormLayout::AllNonFixedFieldsGrow - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - Count - - - - + + @@ -577,14 +565,41 @@ - + + + + Count + + + + + + + ... + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + Equal column widths - + Allow splitting layer items into multiple columns. @@ -1013,18 +1028,17 @@ - - QgsCollapsibleGroupBoxBasic - QGroupBox -
qgscollapsiblegroupbox.h
- 1 -
QgsColorButton QToolButton
qgscolorbutton.h
1
+ + QgsDataDefinedButtonV2 + QToolButton +
qgsdatadefinedbuttonv2.h
+
QgsDoubleSpinBox QDoubleSpinBox @@ -1035,6 +1049,12 @@ QSpinBox
qgsspinbox.h
+ + QgsCollapsibleGroupBoxBasic + QGroupBox +
qgscollapsiblegroupbox.h
+ 1 +
QgsComposerItemComboBox QComboBox @@ -1055,6 +1075,7 @@ scrollArea mMainPropertiesColGroupBox mTitleLineEdit + mLegendTitleDDBtn mTitleAlignCombo mMapComboBox mWrapCharLineEdit @@ -1080,6 +1101,7 @@ mItemFontButton mFontColorButton mColumnsColGroupBox + mColumnsDDBtn mColumnCountSpinBox mEqualColumnWidthCheckBox mSplitLayerCheckBox @@ -1100,6 +1122,7 @@ mIconLabelSpaceSpinBox mBoxSpaceSpinBox mColumnSpaceSpinBox + mLineSpacingSpinBox diff --git a/tests/src/python/test_qgscomposerlegend.py b/tests/src/python/test_qgscomposerlegend.py index 02b95c27b06..365969ad036 100644 --- a/tests/src/python/test_qgscomposerlegend.py +++ b/tests/src/python/test_qgscomposerlegend.py @@ -24,7 +24,9 @@ from qgis.core import (QgsComposerLegend, QgsMarkerSymbol, QgsSingleSymbolRenderer, QgsRectangle, - QgsProject + QgsProject, + QgsComposerObject, + QgsExpressionBasedProperty ) from qgis.testing import (start_app, unittest @@ -200,5 +202,41 @@ class TestQgsComposerLegend(unittest.TestCase): QgsProject.instance().removeMapLayers([point_layer.id()]) + def testDataDefinedTitle(self): + mapSettings = QgsMapSettings() + + composition = QgsComposition(mapSettings, QgsProject.instance()) + composition.setPaperSize(297, 210) + + legend = QgsComposerLegend(composition) + composition.addComposerLegend(legend) + + legend.setTitle('original') + self.assertEqual(legend.title(), 'original') + self.assertEqual(legend.legendSettings().title(), 'original') + + legend.dataDefinedProperties().setProperty(QgsComposerObject.LegendTitle, QgsExpressionBasedProperty("'new'")) + legend.refreshDataDefinedProperty() + self.assertEqual(legend.title(), 'original') + self.assertEqual(legend.legendSettings().title(), 'new') + + def testDataDefinedColumnCount(self): + mapSettings = QgsMapSettings() + + composition = QgsComposition(mapSettings, QgsProject.instance()) + composition.setPaperSize(297, 210) + + legend = QgsComposerLegend(composition) + composition.addComposerLegend(legend) + + legend.setColumnCount(2) + self.assertEqual(legend.columnCount(), 2) + self.assertEqual(legend.legendSettings().columnCount(), 2) + + legend.dataDefinedProperties().setProperty(QgsComposerObject.LegendColumnCount, QgsExpressionBasedProperty("5")) + legend.refreshDataDefinedProperty() + self.assertEqual(legend.columnCount(), 2) + self.assertEqual(legend.legendSettings().columnCount(), 5) + if __name__ == '__main__': unittest.main()