From d6c7569dda4fc5699ef81e0b9c82b7208331718a Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 17 Jan 2017 12:21:19 +1000 Subject: [PATCH] [FEATURE][composer] Data definable item frame and background colors --- python/core/composer/qgscomposerobject.sip | 2 + src/app/composer/qgscomposeritemwidget.cpp | 4 + src/core/composer/qgscomposeritem.cpp | 115 ++++++++++++++----- src/core/composer/qgscomposeritem.h | 27 ++++- src/core/composer/qgscomposerobject.cpp | 2 + src/core/composer/qgscomposerobject.h | 2 + src/ui/composer/qgscomposeritemwidgetbase.ui | 18 ++- tests/src/python/CMakeLists.txt | 1 + tests/src/python/test_qgscomposeritem.py | 66 +++++++++++ 9 files changed, 206 insertions(+), 31 deletions(-) create mode 100644 tests/src/python/test_qgscomposeritem.py diff --git a/python/core/composer/qgscomposerobject.sip b/python/core/composer/qgscomposerobject.sip index 45acb0d6607..6e1bc09b98b 100644 --- a/python/core/composer/qgscomposerobject.sip +++ b/python/core/composer/qgscomposerobject.sip @@ -32,6 +32,8 @@ class QgsComposerObject : QObject, QgsExpressionContextGenerator Transparency, /*!< item transparency */ BlendMode, /*!< item blend mode */ ExcludeFromExports, /*!< exclude item from exports */ + FrameColor, //!< Item frame color + BackgroundColor, //!< Item background color //composer map MapRotation, /*!< map rotation */ MapScale, /*!< map scale */ diff --git a/src/app/composer/qgscomposeritemwidget.cpp b/src/app/composer/qgscomposeritemwidget.cpp index 4852820ac33..f150d7d2d5d 100644 --- a/src/app/composer/qgscomposeritemwidget.cpp +++ b/src/app/composer/qgscomposeritemwidget.cpp @@ -541,6 +541,10 @@ void QgsComposerItemWidget::populateDataDefinedButtons() QgsDataDefinedButtonV2::String, QgsDataDefinedButtonV2::blendModesDesc() ); mConfigObject->registerDataDefinedButton( mExcludePrintsDDBtn, QgsComposerObject::ExcludeFromExports, QgsDataDefinedButtonV2::String, QgsDataDefinedButtonV2::boolDesc() ); + mConfigObject->registerDataDefinedButton( mItemFrameColorDDBtn, QgsComposerObject::FrameColor, + QgsDataDefinedButtonV2::String, QgsDataDefinedButtonV2::colorAlphaDesc() ); + mConfigObject->registerDataDefinedButton( mItemBackgroundColorDDBtn, QgsComposerObject::BackgroundColor, + QgsDataDefinedButtonV2::String, QgsDataDefinedButtonV2::colorAlphaDesc() ); } void QgsComposerItemWidget::setValuesForGuiElements() diff --git a/src/core/composer/qgscomposeritem.cpp b/src/core/composer/qgscomposeritem.cpp index f62a380f16d..fc22a086647 100644 --- a/src/core/composer/qgscomposeritem.cpp +++ b/src/core/composer/qgscomposeritem.cpp @@ -54,7 +54,6 @@ QgsComposerItem::QgsComposerItem( QgsComposition* composition, bool manageZValue , mFrame( false ) , mBackground( true ) , mBackgroundColor( QColor( 255, 255, 255, 255 ) ) - , mFrameJoinStyle( Qt::MiterJoin ) , mItemPositionLocked( false ) , mLastValidViewScaleFactor( -1 ) , mItemRotation( 0 ) @@ -81,9 +80,9 @@ QgsComposerItem::QgsComposerItem( qreal x, qreal y, qreal width, qreal height, Q , mHAlignSnapItem( nullptr ) , mVAlignSnapItem( nullptr ) , mFrame( false ) + , mFrameColor( QColor( 0, 0, 0 ) ) , mBackground( true ) , mBackgroundColor( QColor( 255, 255, 255, 255 ) ) - , mFrameJoinStyle( Qt::MiterJoin ) , mItemPositionLocked( false ) , mLastValidViewScaleFactor( -1 ) , mItemRotation( 0 ) @@ -107,9 +106,9 @@ void QgsComposerItem::init( const bool manageZValue ) { setFlag( QGraphicsItem::ItemIsSelectable, true ); //set default pen and brush - setBrush( QBrush( QColor( 255, 255, 255, 255 ) ) ); - QPen defaultPen( QColor( 0, 0, 0 ) ); - defaultPen.setWidthF( 0.3 ); + setBrush( mBackgroundColor ); + QPen defaultPen( mFrameColor ); + defaultPen.setWidthF( mFrameWidth ); defaultPen.setJoinStyle( mFrameJoinStyle ); setPen( defaultPen ); //let z-Value be managed by composition @@ -192,7 +191,7 @@ bool QgsComposerItem::_writeXml( QDomElement& itemElem, QDomDocument& doc ) cons composerItemElem.setAttribute( QStringLiteral( "height" ), QString::number( rect().height() ) ); composerItemElem.setAttribute( QStringLiteral( "positionMode" ), QString::number( static_cast< int >( mLastUsedPositionMode ) ) ); composerItemElem.setAttribute( QStringLiteral( "zValue" ), QString::number( zValue() ) ); - composerItemElem.setAttribute( QStringLiteral( "outlineWidth" ), QString::number( pen().widthF() ) ); + composerItemElem.setAttribute( QStringLiteral( "outlineWidth" ), QString::number( mFrameWidth ) ); composerItemElem.setAttribute( QStringLiteral( "frameJoinStyle" ), QgsSymbolLayerUtils::encodePenJoinStyle( mFrameJoinStyle ) ); composerItemElem.setAttribute( QStringLiteral( "itemRotation" ), QString::number( mItemRotation ) ); composerItemElem.setAttribute( QStringLiteral( "uuid" ), mUuid ); @@ -212,20 +211,18 @@ bool QgsComposerItem::_writeXml( QDomElement& itemElem, QDomDocument& doc ) cons //frame color QDomElement frameColorElem = doc.createElement( QStringLiteral( "FrameColor" ) ); - QColor frameColor = pen().color(); - frameColorElem.setAttribute( QStringLiteral( "red" ), QString::number( frameColor.red() ) ); - frameColorElem.setAttribute( QStringLiteral( "green" ), QString::number( frameColor.green() ) ); - frameColorElem.setAttribute( QStringLiteral( "blue" ), QString::number( frameColor.blue() ) ); - frameColorElem.setAttribute( QStringLiteral( "alpha" ), QString::number( frameColor.alpha() ) ); + frameColorElem.setAttribute( QStringLiteral( "red" ), QString::number( mFrameColor.red() ) ); + frameColorElem.setAttribute( QStringLiteral( "green" ), QString::number( mFrameColor.green() ) ); + frameColorElem.setAttribute( QStringLiteral( "blue" ), QString::number( mFrameColor.blue() ) ); + frameColorElem.setAttribute( QStringLiteral( "alpha" ), QString::number( mFrameColor.alpha() ) ); composerItemElem.appendChild( frameColorElem ); //background color QDomElement bgColorElem = doc.createElement( QStringLiteral( "BackgroundColor" ) ); - QColor bgColor = brush().color(); - bgColorElem.setAttribute( QStringLiteral( "red" ), QString::number( bgColor.red() ) ); - bgColorElem.setAttribute( QStringLiteral( "green" ), QString::number( bgColor.green() ) ); - bgColorElem.setAttribute( QStringLiteral( "blue" ), QString::number( bgColor.blue() ) ); - bgColorElem.setAttribute( QStringLiteral( "alpha" ), QString::number( bgColor.alpha() ) ); + bgColorElem.setAttribute( QStringLiteral( "red" ), QString::number( mBackgroundColor.red() ) ); + bgColorElem.setAttribute( QStringLiteral( "green" ), QString::number( mBackgroundColor.green() ) ); + bgColorElem.setAttribute( QStringLiteral( "blue" ), QString::number( mBackgroundColor.blue() ) ); + bgColorElem.setAttribute( QStringLiteral( "alpha" ), QString::number( mBackgroundColor.alpha() ) ); composerItemElem.appendChild( bgColorElem ); //blend mode @@ -335,6 +332,8 @@ bool QgsComposerItem::_readXml( const QDomElement& itemElem, const QDomDocument& setZValue( itemElem.attribute( QStringLiteral( "zValue" ) ).toDouble() ); + QgsExpressionContext context = createExpressionContext(); + //pen QDomNodeList frameColorList = itemElem.elementsByTagName( QStringLiteral( "FrameColor" ) ); if ( !frameColorList.isEmpty() ) @@ -353,10 +352,14 @@ bool QgsComposerItem::_readXml( const QDomElement& itemElem, const QDomDocument& if ( redOk && greenOk && blueOk && alphaOk && widthOk ) { - QPen framePen( QColor( penRed, penGreen, penBlue, penAlpha ) ); - framePen.setWidthF( penWidth ); + mFrameColor = QColor( penRed, penGreen, penBlue, penAlpha ); + mFrameWidth = penWidth; + QPen framePen( mFrameColor ); + framePen.setWidthF( mFrameWidth ); framePen.setJoinStyle( mFrameJoinStyle ); setPen( framePen ); + //apply any data defined settings + refreshFrameColor( false, context ); } } @@ -373,9 +376,11 @@ bool QgsComposerItem::_readXml( const QDomElement& itemElem, const QDomDocument& bgAlpha = bgColorElem.attribute( QStringLiteral( "alpha" ) ).toDouble( &alphaOk ); if ( redOk && greenOk && blueOk && alphaOk ) { - QColor brushColor( bgRed, bgGreen, bgBlue, bgAlpha ); - setBackgroundColor( brushColor ); + mBackgroundColor = QColor( bgRed, bgGreen, bgBlue, bgAlpha ); + setBrush( QBrush( mBackgroundColor, Qt::SolidPattern ) ); } + //apply any data defined settings + refreshBackgroundColor( false, context ); } //blend mode @@ -407,26 +412,31 @@ void QgsComposerItem::setFrameEnabled( const bool drawFrame ) void QgsComposerItem::setFrameOutlineColor( const QColor &color ) { - QPen itemPen = pen(); - if ( itemPen.color() == color ) + if ( mFrameColor == color ) { //no change return; } - itemPen.setColor( color ); + mFrameColor = color; + QPen itemPen = pen(); + itemPen.setColor( mFrameColor ); setPen( itemPen ); + // apply any datadefined overrides + QgsExpressionContext context = createExpressionContext(); + refreshFrameColor( true, context ); emit frameChanged(); } void QgsComposerItem::setFrameOutlineWidth( const double outlineWidth ) { - QPen itemPen = pen(); - if ( qgsDoubleNear( itemPen.widthF(), outlineWidth ) ) + if ( qgsDoubleNear( mFrameWidth, outlineWidth ) ) { //no change return; } - itemPen.setWidthF( outlineWidth ); + mFrameWidth = outlineWidth; + QPen itemPen = pen(); + itemPen.setWidthF( mFrameWidth ); setPen( itemPen ); emit frameChanged(); } @@ -844,6 +854,9 @@ void QgsComposerItem::setBackgroundColor( const QColor& backgroundColor ) { mBackgroundColor = backgroundColor; setBrush( QBrush( mBackgroundColor, Qt::SolidPattern ) ); + // apply any datadefined overrides + QgsExpressionContext context = createExpressionContext(); + refreshBackgroundColor( true, context ); } void QgsComposerItem::setBlendMode( const QPainter::CompositionMode blendMode ) @@ -897,6 +910,48 @@ void QgsComposerItem::refreshTransparency( const bool updateItem, const QgsExpre } } +void QgsComposerItem::refreshFrameColor( const bool updateItem, const QgsExpressionContext& context ) +{ + //data defined outline color set? + bool ok = false; + QColor frameColor = mProperties.valueAsColor( QgsComposerObject::FrameColor, context, mFrameColor, &ok ); + if ( ok ) + { + QPen itemPen = pen(); + itemPen.setColor( frameColor ); + setPen( itemPen ); + } + else + { + QPen itemPen = pen(); + itemPen.setColor( mFrameColor ); + setPen( itemPen ); + } + if ( updateItem ) + { + update(); + } +} + +void QgsComposerItem::refreshBackgroundColor( const bool updateItem, const QgsExpressionContext& context ) +{ + //data defined color set? + bool ok = false; + QColor backgroundColor = mProperties.valueAsColor( QgsComposerObject::BackgroundColor, context, mBackgroundColor, &ok ); + if ( ok ) + { + setBrush( QBrush( backgroundColor, Qt::SolidPattern ) ); + } + else + { + setBrush( QBrush( mBackgroundColor, Qt::SolidPattern ) ); + } + if ( updateItem ) + { + update(); + } +} + void QgsComposerItem::setEffectsEnabled( const bool effectsEnabled ) { //enable or disable the QgsComposerEffect applied to this item @@ -1087,6 +1142,14 @@ void QgsComposerItem::refreshDataDefinedProperty( const QgsComposerObject::DataD { refreshBlendMode( *evalContext ); } + if ( property == QgsComposerObject::FrameColor || property == QgsComposerObject::AllProperties ) + { + refreshFrameColor( false, *evalContext ); + } + if ( property == QgsComposerObject::BackgroundColor || property == QgsComposerObject::AllProperties ) + { + refreshBackgroundColor( false, *evalContext ); + } if ( property == QgsComposerObject::ExcludeFromExports || property == QgsComposerObject::AllProperties ) { bool exclude = mExcludeFromExports; diff --git a/src/core/composer/qgscomposeritem.h b/src/core/composer/qgscomposeritem.h index 177e07ce1a8..55888d35422 100644 --- a/src/core/composer/qgscomposeritem.h +++ b/src/core/composer/qgscomposeritem.h @@ -260,7 +260,7 @@ class CORE_EXPORT QgsComposerItem: public QgsComposerObject, public QGraphicsRec * @see frameJoinStyle * @see setFrameOutlineColor */ - QColor frameOutlineColor() const { return pen().color(); } + QColor frameOutlineColor() const { return mFrameColor; } /** Sets frame outline width * @param outlineWidth new width for outline frame @@ -280,7 +280,7 @@ class CORE_EXPORT QgsComposerItem: public QgsComposerObject, public QGraphicsRec * @see frameJoinStyle * @see frameOutlineColor */ - double frameOutlineWidth() const { return pen().widthF(); } + double frameOutlineWidth() const { return mFrameWidth; } /** Returns the join style used for drawing the item's frame * @returns Join style for outline frame @@ -564,12 +564,17 @@ class CORE_EXPORT QgsComposerItem: public QgsComposerObject, public QGraphicsRec //! True if item fram needs to be painted bool mFrame; + //! Item frame color + QColor mFrameColor; + //! Item frame width + double mFrameWidth = 0.3; + //! Frame join style + Qt::PenJoinStyle mFrameJoinStyle = Qt::MiterJoin; + //! True if item background needs to be painted bool mBackground; //! Background color QColor mBackgroundColor; - //! Frame join style - Qt::PenJoinStyle mFrameJoinStyle; /** True if item position and size cannot be changed with mouse move */ @@ -706,6 +711,20 @@ class CORE_EXPORT QgsComposerItem: public QgsComposerObject, public QGraphicsRec */ void refreshTransparency( const bool updateItem = true, const QgsExpressionContext &context = QgsExpressionContext() ); + /** Refresh item's frame color, considering data defined transparency + * @param updateItem set to false to prevent the item being automatically updated + * after the frame color is set + * @param context expression context for evaulating data defined transparency + */ + void refreshFrameColor( const bool updateItem = true, const QgsExpressionContext &context = QgsExpressionContext() ); + + /** Refresh item's transparency, considering data defined transparency + * @param updateItem set to false to prevent the item being automatically updated + * after the background color is set + * @param context expression context for evaulating data defined transparency + */ + void refreshBackgroundColor( const bool updateItem = true, const QgsExpressionContext &context = QgsExpressionContext() ); + /** Refresh item's blend mode, considering data defined blend mode * @note this method was added in version 2.5 */ diff --git a/src/core/composer/qgscomposerobject.cpp b/src/core/composer/qgscomposerobject.cpp index 711c957a272..ecd52cf6f93 100644 --- a/src/core/composer/qgscomposerobject.cpp +++ b/src/core/composer/qgscomposerobject.cpp @@ -40,6 +40,8 @@ const QgsPropertyDefinition QgsComposerObject::sPropertyNameMap { QgsComposerObject::Transparency, "dataDefinedTransparency" }, { QgsComposerObject::BlendMode, "dataDefinedBlendMode" }, { QgsComposerObject::ExcludeFromExports, "dataDefinedExcludeExports"}, + { QgsComposerObject::FrameColor, "dataDefinedFrameColor"}, + { QgsComposerObject::BackgroundColor, "dataDefinedBackgroundColor"}, { QgsComposerObject::MapRotation, "dataDefinedMapRotation" }, { QgsComposerObject::MapScale, "dataDefinedMapScale" }, { QgsComposerObject::MapXMin, "dataDefinedMapXMin" }, diff --git a/src/core/composer/qgscomposerobject.h b/src/core/composer/qgscomposerobject.h index f0b2ee46a42..db19bf41de8 100644 --- a/src/core/composer/qgscomposerobject.h +++ b/src/core/composer/qgscomposerobject.h @@ -59,6 +59,8 @@ class CORE_EXPORT QgsComposerObject: public QObject, public QgsExpressionContext Transparency, //!< Item transparency BlendMode, //!< Item blend mode ExcludeFromExports, //!< Exclude item from exports + FrameColor, //!< Item frame color + BackgroundColor, //!< Item background color //composer map MapRotation, //!< Map rotation MapScale, //!< Map scale diff --git a/src/ui/composer/qgscomposeritemwidgetbase.ui b/src/ui/composer/qgscomposeritemwidgetbase.ui index 3cec8d671a5..aeca1a80226 100644 --- a/src/ui/composer/qgscomposeritemwidgetbase.ui +++ b/src/ui/composer/qgscomposeritemwidgetbase.ui @@ -7,7 +7,7 @@ 0 0 290 - 847 + 1017 @@ -453,6 +453,13 @@ + + + + ... + + + @@ -572,6 +579,13 @@ + + + + ... + + + @@ -816,10 +830,12 @@ mItemRotationDDBtn mFrameGroupBox mFrameColorButton + mItemFrameColorDDBtn mOutlineWidthSpinBox mFrameJoinStyleCombo mBackgroundGroupBox mBackgroundColorButton + mItemBackgroundColorDDBtn groupBox mItemIdLineEdit groupRendering diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index f44f696d435..0c201fa30aa 100644 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -25,6 +25,7 @@ ADD_PYTHON_TEST(PyQgsColorScheme test_qgscolorscheme.py) ADD_PYTHON_TEST(PyQgsColorSchemeRegistry test_qgscolorschemeregistry.py) ADD_PYTHON_TEST(PyQgsComposerEffects test_qgscomposereffects.py) ADD_PYTHON_TEST(PyQgsComposerHtml test_qgscomposerhtml.py) +ADD_PYTHON_TEST(PyQgsComposerItem test_qgscomposeritem.py) ADD_PYTHON_TEST(PyQgsComposerLabel test_qgscomposerlabel.py) ADD_PYTHON_TEST(PyQgsComposerLegend test_qgscomposerlegend.py) ADD_PYTHON_TEST(PyQgsComposerMap test_qgscomposermap.py) diff --git a/tests/src/python/test_qgscomposeritem.py b/tests/src/python/test_qgscomposeritem.py new file mode 100644 index 00000000000..3c01ed07d1b --- /dev/null +++ b/tests/src/python/test_qgscomposeritem.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +"""QGIS Unit tests for QgsComposerItem. + +.. note:: This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +""" +__author__ = '(C) 2017 by Nyall Dawson' +__date__ = '17/01/2017' +__copyright__ = 'Copyright 2017, The QGIS Project' +# This will get replaced with a git SHA1 when you do a git archive +__revision__ = '$Format:%H$' +import qgis # NOQA + +from qgis.testing import start_app, unittest +from qgis.core import (QgsProject, + QgsMapSettings, + QgsComposition, + QgsComposerLabel, + QgsComposerObject, + QgsExpressionBasedProperty) +from qgis.PyQt.QtGui import (QColor) +start_app() + + +class TestQgsComposerItem(unittest.TestCase): + + def testDataDefinedFrameColor(self): + mapSettings = QgsMapSettings() + + composition = QgsComposition(mapSettings, QgsProject.instance()) + composition.setPaperSize(297, 210) + + item = QgsComposerLabel(composition) + composition.addComposerLabel(item) + + item.setFrameOutlineColor(QColor(255, 0, 0)) + self.assertEqual(item.frameOutlineColor(), QColor(255, 0, 0)) + self.assertEqual(item.pen().color().name(), QColor(255, 0, 0).name()) + + item.dataDefinedProperties().setProperty(QgsComposerObject.FrameColor, QgsExpressionBasedProperty("'blue'")) + item.refreshDataDefinedProperty() + self.assertEqual(item.frameOutlineColor(), QColor(255, 0, 0)) # should not change + self.assertEqual(item.pen().color().name(), QColor(0, 0, 255).name()) + + def testDataDefinedBackgroundColor(self): + mapSettings = QgsMapSettings() + + composition = QgsComposition(mapSettings, QgsProject.instance()) + composition.setPaperSize(297, 210) + + item = QgsComposerLabel(composition) + composition.addComposerLabel(item) + + item.setBackgroundColor(QColor(255, 0, 0)) + self.assertEqual(item.backgroundColor(), QColor(255, 0, 0)) + self.assertEqual(item.brush().color().name(), QColor(255, 0, 0).name()) + + item.dataDefinedProperties().setProperty(QgsComposerObject.BackgroundColor, QgsExpressionBasedProperty("'blue'")) + item.refreshDataDefinedProperty() + self.assertEqual(item.backgroundColor(), QColor(255, 0, 0)) # should not change + self.assertEqual(item.brush().color().name(), QColor(0, 0, 255).name()) + +if __name__ == '__main__': + unittest.main()