[FEATURE][composer] Data defined item rotation. Funded by Canton of Neuchâtel, Switzerland

This commit is contained in:
Nyall Dawson 2014-07-05 20:08:20 +10:00
parent a3c8289942
commit 0fd99d2820
7 changed files with 165 additions and 53 deletions

View File

@ -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()

View File

@ -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.

View File

@ -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;
}

View File

@ -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 );

View File

@ -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 );

View File

@ -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.

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>376</width>
<height>693</height>
<height>695</height>
</rect>
</property>
<property name="windowTitle">
@ -251,20 +251,31 @@
</widget>
</item>
<item row="0" column="1">
<widget class="QDoubleSpinBox" name="mItemRotationSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="suffix">
<string> °</string>
</property>
<property name="maximum">
<double>360.000000000000000</double>
</property>
</widget>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QDoubleSpinBox" name="mItemRotationSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="suffix">
<string> °</string>
</property>
<property name="maximum">
<double>360.000000000000000</double>
</property>
</widget>
</item>
<item>
<widget class="QgsDataDefinedButton" name="mItemRotationDDBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>