From 0fd99d2820387dec293beea288212c6307b25c10 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 5 Jul 2014 20:08:20 +1000 Subject: [PATCH] =?UTF-8?q?[FEATURE][composer]=20Data=20defined=20item=20r?= =?UTF-8?q?otation.=20Funded=20by=20Canton=20of=20Neuch=C3=A2tel,=20Switze?= =?UTF-8?q?rland?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python/core/composer/qgscomposeritem.sip | 21 +++++- python/core/composer/qgscomposition.sip | 5 +- src/app/composer/qgscomposeritemwidget.cpp | 17 ++++- src/core/composer/qgscomposeritem.cpp | 87 ++++++++++++++++------ src/core/composer/qgscomposeritem.h | 42 +++++++++-- src/core/composer/qgscomposition.h | 5 +- src/ui/qgscomposeritemwidgetbase.ui | 41 ++++++---- 7 files changed, 165 insertions(+), 53 deletions(-) diff --git a/python/core/composer/qgscomposeritem.sip b/python/core/composer/qgscomposeritem.sip index 066c787e5a5..03af148e046 100644 --- a/python/core/composer/qgscomposeritem.sip +++ b/python/core/composer/qgscomposeritem.sip @@ -163,6 +163,16 @@ class QgsComposerItem : QObject, QGraphicsRectItem MapYMax /*< map extent y maximum */ }; + /** Specifies whether the value returned by a function should be the original, user + * set value, or the current evaluated value for the property. This may differ if + * a property has a data defined expression active. + */ + enum PropertyValueType + { + EvaluatedValue = 0, /*< return the current evaluated value for the property */ + OriginalValue /*< return the original, user set value */ + }; + /**Constructor @param composition parent composition @param manageZValue true if the z-Value of this object should be managed by mComposition*/ @@ -458,9 +468,14 @@ class QgsComposerItem : QObject, QGraphicsRectItem @note this method was added in version 1.2*/ bool positionLock() const; - /**Returns the rotation for the composer item - @note this method was added in version 2.1*/ - double itemRotation() const; + /**Returns the current rotation for the composer item. + * @returns rotation for composer item + * @param valueType controls whether the returned value is the user specified rotation, + * or the current evaluated rotation (which may be affected by data driven rotation + * settings). + * @note this method was added in version 2.1 + */ + double itemRotation( PropertyValueType valueType = EvaluatedValue ) const; /**Returns the rotation for the composer item * @deprecated Use itemRotation() diff --git a/python/core/composer/qgscomposition.sip b/python/core/composer/qgscomposition.sip index 6dd1d89be56..5e60253425b 100644 --- a/python/core/composer/qgscomposition.sip +++ b/python/core/composer/qgscomposition.sip @@ -470,8 +470,9 @@ class QgsComposition : QGraphicsScene /**Forces items in the composition to refresh. For instance, this causes maps to redraw * and rebuild cached images, html items to reload their source url, and attribute tables - * to refresh their contents. - @note added in version 2.3*/ + * to refresh their contents. Calling this also triggers a recalculation of all data defined + * attributes within the composition. + * @note added in version 2.3*/ void refreshItems(); /**Clears any selected items and sets an item as the current selection. diff --git a/src/app/composer/qgscomposeritemwidget.cpp b/src/app/composer/qgscomposeritemwidget.cpp index 2b430f103db..64ff1e84811 100644 --- a/src/app/composer/qgscomposeritemwidget.cpp +++ b/src/app/composer/qgscomposeritemwidget.cpp @@ -150,6 +150,10 @@ QgsComposerItemWidget::QgsComposerItemWidget( QWidget* parent, QgsComposerItem* } //connect data defined buttons + connect( mItemRotationDDBtn, SIGNAL( dataDefinedChanged( const QString& ) ), this, SLOT( updateDataDefinedProperty( ) ) ); + connect( mItemRotationDDBtn, SIGNAL( dataDefinedActivated( bool ) ), this, SLOT( updateDataDefinedProperty( ) ) ); + connect( mItemRotationDDBtn, SIGNAL( dataDefinedActivated( bool ) ), mItemRotationSpinBox, SLOT( setDisabled( bool ) ) ); + connect( mTransparencyDDBtn, SIGNAL( dataDefinedChanged( const QString& ) ), this, SLOT( updateDataDefinedProperty( ) ) ); connect( mTransparencyDDBtn, SIGNAL( dataDefinedActivated( bool ) ), this, SLOT( updateDataDefinedProperty( ) ) ); connect( mTransparencyDDBtn, SIGNAL( dataDefinedActivated( bool ) ), mTransparencySlider, SLOT( setDisabled( bool ) ) ); @@ -494,7 +498,7 @@ void QgsComposerItemWidget::setValuesForGuiNonPositionElements() mBlendModeCombo->setBlendMode( mItem->blendMode() ); mTransparencySlider->setValue( mItem->transparency() ); mTransparencySpnBx->setValue( mItem->transparency() ); - mItemRotationSpinBox->setValue( mItem->itemRotation() ); + mItemRotationSpinBox->setValue( mItem->itemRotation( QgsComposerItem::OriginalValue ) ); mBackgroundColorButton->blockSignals( false ); mFrameColorButton->blockSignals( false ); @@ -514,28 +518,37 @@ void QgsComposerItemWidget::populateDataDefinedButtons() QgsVectorLayer* vl = atlasCoverageLayer(); //block signals from data defined buttons + mItemRotationDDBtn->blockSignals( true ); mTransparencyDDBtn->blockSignals( true ); mBlendModeDDBtn->blockSignals( true ); //initialise buttons to use atlas coverage layer + mItemRotationDDBtn->init( vl, mItem->dataDefinedProperty( QgsComposerItem::ItemRotation ), + QgsDataDefinedButton::AnyType, QgsDataDefinedButton::double180RotDesc() ); mTransparencyDDBtn->init( vl, mItem->dataDefinedProperty( QgsComposerItem::Transparency ), QgsDataDefinedButton::AnyType, QgsDataDefinedButton::intTranspDesc() ); mBlendModeDDBtn->init( vl, mItem->dataDefinedProperty( QgsComposerItem::BlendMode ), QgsDataDefinedButton::String, QgsDataDefinedButton::blendModesDesc() ); //initial state of controls - disable related controls when dd buttons are active + mItemRotationSpinBox->setEnabled( !mItemRotationDDBtn->isActive() ); mTransparencySlider->setEnabled( !mTransparencyDDBtn->isActive() ); mTransparencySpnBx->setEnabled( !mTransparencyDDBtn->isActive() ); mBlendModeCombo->setEnabled( !mBlendModeDDBtn->isActive() ); //unblock signals from data defined buttons + mItemRotationDDBtn->blockSignals( false ); mTransparencyDDBtn->blockSignals( false ); mBlendModeDDBtn->blockSignals( false ); } QgsComposerItem::DataDefinedProperty QgsComposerItemWidget::ddPropertyForWidget( QgsDataDefinedButton* widget ) { - if ( widget == mTransparencyDDBtn ) + if ( widget == mItemRotationDDBtn ) + { + return QgsComposerItem::ItemRotation; + } + else if ( widget == mTransparencyDDBtn ) { return QgsComposerItem::Transparency; } diff --git a/src/core/composer/qgscomposeritem.cpp b/src/core/composer/qgscomposeritem.cpp index 2d0ef12423d..2fb5b758182 100644 --- a/src/core/composer/qgscomposeritem.cpp +++ b/src/core/composer/qgscomposeritem.cpp @@ -61,6 +61,7 @@ QgsComposerItem::QgsComposerItem( QgsComposition* composition, bool manageZValue , mItemPositionLocked( false ) , mLastValidViewScaleFactor( -1 ) , mItemRotation( 0 ) + , mEvaluatedItemRotation( 0 ) , mBlendMode( QPainter::CompositionMode_SourceOver ) , mEffectsEnabled( true ) , mTransparency( 0 ) @@ -87,6 +88,7 @@ QgsComposerItem::QgsComposerItem( qreal x, qreal y, qreal width, qreal height, Q , mItemPositionLocked( false ) , mLastValidViewScaleFactor( -1 ) , mItemRotation( 0 ) + , mEvaluatedItemRotation( 0 ) , mBlendMode( QPainter::CompositionMode_SourceOver ) , mEffectsEnabled( true ) , mTransparency( 0 ) @@ -120,6 +122,7 @@ void QgsComposerItem::init( bool manageZValue ) setGraphicsEffect( mEffect ); // data defined strings + mDataDefinedNames.insert( ItemRotation, QString( "dataDefinedRotation" ) ); mDataDefinedNames.insert( Transparency, QString( "dataDefinedTransparency" ) ); mDataDefinedNames.insert( BlendMode, QString( "dataDefinedBlendMode" ) ); @@ -531,6 +534,11 @@ void QgsComposerItem::setPositionLock( bool lock ) mItemPositionLocked = lock; } +double QgsComposerItem::itemRotation( PropertyValueType valueType ) const +{ + return valueType == EvaluatedValue ? mEvaluatedItemRotation : mItemRotation; +} + void QgsComposerItem::move( double dx, double dy ) { QRectF newSceneRect( pos().x() + dx, pos().y() + dy, rect().width(), rect().height() ); @@ -612,7 +620,7 @@ void QgsComposerItem::setItemPosition( double x, double y, double width, double { //adjust position to account for frame size - if ( mItemRotation == 0 ) + if ( mEvaluatedItemRotation == 0 ) { upperLeftX += estimatedFrameBleed(); upperLeftY += estimatedFrameBleed(); @@ -621,7 +629,7 @@ void QgsComposerItem::setItemPosition( double x, double y, double width, double { //adjust position for item rotation QLineF lineToItemOrigin = QLineF( 0, 0, estimatedFrameBleed(), estimatedFrameBleed() ); - lineToItemOrigin.setAngle( -45 - mItemRotation ); + lineToItemOrigin.setAngle( -45 - mEvaluatedItemRotation ); upperLeftX += lineToItemOrigin.x2(); upperLeftY += lineToItemOrigin.y2(); } @@ -935,20 +943,7 @@ void QgsComposerItem::setRotation( double r ) void QgsComposerItem::setItemRotation( double r, bool adjustPosition ) { - if ( adjustPosition ) - { - //adjustPosition set, so shift the position of the item so that rotation occurs around item center - //create a line from the centrepoint of the rect() to its origin, in scene coordinates - QLineF refLine = QLineF( mapToScene( QPointF( rect().width() / 2.0, rect().height() / 2.0 ) ) , mapToScene( QPointF( 0 , 0 ) ) ); - //rotate this line by the current rotation angle - refLine.setAngle( refLine.angle() - r + mItemRotation ); - //get new end point of line - this is the new item position - QPointF rotatedReferencePoint = refLine.p2(); - setPos( rotatedReferencePoint ); - emit sizeChanged(); - } - - if ( r > 360 ) + if ( r >= 360 ) { mItemRotation = (( int )r ) % 360; } @@ -957,17 +952,59 @@ void QgsComposerItem::setItemRotation( double r, bool adjustPosition ) mItemRotation = r; } - setTransformOriginPoint( 0, 0 ); - QGraphicsItem::setRotation( mItemRotation ); + refreshRotation( true, adjustPosition ); +} - emit itemRotationChanged( r ); - update(); +void QgsComposerItem::refreshRotation( bool updateItem , bool adjustPosition ) +{ + double rotation = mItemRotation; + + //data defined rotation set? + QVariant exprVal; + if ( dataDefinedEvaluate( QgsComposerItem::ItemRotation, exprVal ) ) + { + bool ok; + double rotD = exprVal.toDouble( &ok ); + QgsDebugMsg( QString( "exprVal Rotation:%1" ).arg( rotD ) ); + if ( ok ) + { + rotation = rotD; + } + } + + if ( adjustPosition ) + { + //adjustPosition set, so shift the position of the item so that rotation occurs around item center + //create a line from the centrepoint of the rect() to its origin, in scene coordinates + QLineF refLine = QLineF( mapToScene( QPointF( rect().width() / 2.0, rect().height() / 2.0 ) ) , mapToScene( QPointF( 0 , 0 ) ) ); + //rotate this line by the current rotation angle + refLine.setAngle( refLine.angle() - rotation + mEvaluatedItemRotation ); + //get new end point of line - this is the new item position + QPointF rotatedReferencePoint = refLine.p2(); + setPos( rotatedReferencePoint ); + emit sizeChanged(); + } + + setTransformOriginPoint( 0, 0 ); + QGraphicsItem::setRotation( rotation ); + + mEvaluatedItemRotation = rotation; + + emit itemRotationChanged( rotation ); + + //update bounds of scene, since rotation may affect this + mComposition->updateBounds(); + + if ( updateItem ) + { + update(); + } } bool QgsComposerItem::imageSizeConsideringRotation( double& width, double& height ) const { //kept for api compatibility with QGIS 2.0, use item rotation - return imageSizeConsideringRotation( width, height, mItemRotation ); + return imageSizeConsideringRotation( width, height, mEvaluatedItemRotation ); } QRectF QgsComposerItem::largestRotatedRectWithinBounds( QRectF originalRect, QRectF boundsRect, double rotation ) const @@ -1108,7 +1145,7 @@ bool QgsComposerItem::imageSizeConsideringRotation( double& width, double& heigh bool QgsComposerItem::cornerPointOnRotatedAndScaledRect( double& x, double& y, double width, double height ) const { //kept for api compatibility with QGIS 2.0, use item rotation - return cornerPointOnRotatedAndScaledRect( x, y, width, height, mItemRotation ); + return cornerPointOnRotatedAndScaledRect( x, y, width, height, mEvaluatedItemRotation ); } bool QgsComposerItem::cornerPointOnRotatedAndScaledRect( double& x, double& y, double width, double height, double rotation ) const @@ -1151,7 +1188,7 @@ bool QgsComposerItem::cornerPointOnRotatedAndScaledRect( double& x, double& y, d void QgsComposerItem::sizeChangedByRotation( double& width, double& height ) { //kept for api compatibility with QGIS 2.0, use item rotation - return sizeChangedByRotation( width, height, mItemRotation ); + return sizeChangedByRotation( width, height, mEvaluatedItemRotation ); } void QgsComposerItem::sizeChangedByRotation( double& width, double& height, double rotation ) @@ -1289,6 +1326,10 @@ void QgsComposerItem::repaint() void QgsComposerItem::refreshDataDefinedProperty( QgsComposerItem::DataDefinedProperty property ) { //update data defined properties and redraw item to match + if ( property == QgsComposerItem::ItemRotation || property == QgsComposerItem::AllProperties ) + { + refreshRotation( false, true ); + } if ( property == QgsComposerItem::Transparency || property == QgsComposerItem::AllProperties ) { refreshTransparency( false ); diff --git a/src/core/composer/qgscomposeritem.h b/src/core/composer/qgscomposeritem.h index c9e23e8c030..89cfba2f9b4 100644 --- a/src/core/composer/qgscomposeritem.h +++ b/src/core/composer/qgscomposeritem.h @@ -118,6 +118,16 @@ class CORE_EXPORT QgsComposerItem: public QObject, public QGraphicsRectItem MapYMax /*< map extent y maximum */ }; + /** Specifies whether the value returned by a function should be the original, user + * set value, or the current evaluated value for the property. This may differ if + * a property has a data defined expression active. + */ + enum PropertyValueType + { + EvaluatedValue = 0, /*< return the current evaluated value for the property */ + OriginalValue /*< return the original, user set value */ + }; + /**Constructor @param composition parent composition @param manageZValue true if the z-Value of this object should be managed by mComposition*/ @@ -139,7 +149,7 @@ class CORE_EXPORT QgsComposerItem: public QObject, public QGraphicsRectItem virtual void setSelected( bool s ); /** \brief Is selected */ - virtual bool selected() const {return QGraphicsRectItem::isSelected();}; + virtual bool selected() const { return QGraphicsRectItem::isSelected(); } /** stores state in project */ virtual bool writeSettings(); @@ -414,15 +424,20 @@ class CORE_EXPORT QgsComposerItem: public QObject, public QGraphicsRectItem @note this method was added in version 1.2*/ bool positionLock() const { return mItemPositionLocked; } - /**Returns the rotation for the composer item - @note this method was added in version 2.1*/ - double itemRotation() const { return mItemRotation; } + /**Returns the current rotation for the composer item. + * @returns rotation for composer item + * @param valueType controls whether the returned value is the user specified rotation, + * or the current evaluated rotation (which may be affected by data driven rotation + * settings). + * @note this method was added in version 2.1 + */ + double itemRotation( PropertyValueType valueType = EvaluatedValue ) const; /**Returns the rotation for the composer item * @deprecated Use itemRotation() * instead */ - Q_DECL_DEPRECATED double rotation() const { return mItemRotation; } + Q_DECL_DEPRECATED double rotation() const { return mEvaluatedItemRotation; } /**Updates item, with the possibility to do custom update for subclasses*/ virtual void updateItem() { QGraphicsRectItem::update(); } @@ -541,6 +556,10 @@ class CORE_EXPORT QgsComposerItem: public QObject, public QGraphicsRectItem /**Item rotation in degrees, clockwise*/ double mItemRotation; + /**Temporary evaluated item rotation in degrees, clockwise. Data defined rotation may mean + * this value differs from mItemRotation. + */ + double mEvaluatedItemRotation; /**Composition blend mode for item*/ QPainter::CompositionMode mBlendMode; @@ -678,13 +697,24 @@ class CORE_EXPORT QgsComposerItem: public QObject, public QGraphicsRectItem /**Map of current data defined properties*/ QMap< QgsComposerItem::DataDefinedProperty, QgsDataDefined* > mDataDefinedProperties; + /**Refresh item's rotation, considering data defined rotation setting + *@param updateItem set to false to prevent the item being automatically updated + *@param rotateAroundCenter set to true to rotate the item around its center rather + * than its origin + * @note this method was added in version 2.5 + */ + void refreshRotation( bool updateItem = true, bool rotateAroundCenter = false ); + /**Refresh item's transparency, considering data defined transparency *@param updateItem set to false to prevent the item being automatically updated * after the transparency is set + * @note this method was added in version 2.5 */ void refreshTransparency( bool updateItem = true ); - /**Refresh item's blend mode, considering data defined blend mode*/ + /**Refresh item's blend mode, considering data defined blend mode + * @note this method was added in version 2.5 + */ void refreshBlendMode(); void init( bool manageZValue ); diff --git a/src/core/composer/qgscomposition.h b/src/core/composer/qgscomposition.h index 433b43498a8..844163d7c3a 100644 --- a/src/core/composer/qgscomposition.h +++ b/src/core/composer/qgscomposition.h @@ -531,8 +531,9 @@ class CORE_EXPORT QgsComposition : public QGraphicsScene /**Forces items in the composition to refresh. For instance, this causes maps to redraw * and rebuild cached images, html items to reload their source url, and attribute tables - * to refresh their contents. - @note added in version 2.3*/ + * to refresh their contents. Calling this also triggers a recalculation of all data defined + * attributes within the composition. + * @note added in version 2.3*/ void refreshItems(); /**Clears any selected items and sets an item as the current selection. diff --git a/src/ui/qgscomposeritemwidgetbase.ui b/src/ui/qgscomposeritemwidgetbase.ui index ab6ff7cf965..42fd55bac3d 100644 --- a/src/ui/qgscomposeritemwidgetbase.ui +++ b/src/ui/qgscomposeritemwidgetbase.ui @@ -7,7 +7,7 @@ 0 0 376 - 693 + 695 @@ -251,20 +251,31 @@ - - - - 0 - 0 - - - - ° - - - 360.000000000000000 - - + + + + + + 0 + 0 + + + + ° + + + 360.000000000000000 + + + + + + + ... + + + +