Skip to content

Commit

Permalink
Work on functional rotation for items
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Nov 7, 2017
1 parent ce79ff3 commit 8629225
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 43 deletions.
22 changes: 19 additions & 3 deletions python/core/layout/qgslayoutitem.sip
Expand Up @@ -275,6 +275,14 @@ class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem, QgsLayoutUndoObjectInt

double itemRotation() const;
%Docstring
Returns the current rotation for the item, in degrees clockwise.

Note that this method will always return the user-set rotation for the item,
which may differ from the current item rotation (if data defined rotation
settings are present). Use QGraphicsItem.rotation() to obtain the current
item rotation.

.. seealso:: setItemRotation()
:rtype: float
%End

Expand Down Expand Up @@ -506,9 +514,13 @@ class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem, QgsLayoutUndoObjectInt
refreshed.
%End

virtual void setItemRotation( const double rotation );
virtual void setItemRotation( double rotation, bool adjustPosition = true );
%Docstring
Sets the layout item's ``rotation``, in degrees clockwise. This rotation occurs around the center of the item.
Sets the layout item's ``rotation``, in degrees clockwise.

If ``adjustPosition`` is true, then this rotation occurs around the center of the item.
If ``adjustPosition`` is false, rotation occurs around the item origin.

.. seealso:: itemRotation()
.. seealso:: rotateItem()
%End
Expand Down Expand Up @@ -602,10 +614,14 @@ class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem, QgsLayoutUndoObjectInt
.. seealso:: refreshItemSize()
%End

void refreshItemRotation();
void refreshItemRotation( QPointF *origin = 0 );
%Docstring
Refreshes an item's rotation by rechecking it against any possible overrides
such as data defined rotation.

The optional ``origin`` point specifies the origin (in item coordinates)
around which the rotation should be applied.

.. seealso:: refreshItemSize()
.. seealso:: refreshItemPosition()
%End
Expand Down
72 changes: 53 additions & 19 deletions src/core/layout/qgslayoutitem.cpp
Expand Up @@ -427,7 +427,7 @@ bool QgsLayoutItem::shouldDrawItem() const

double QgsLayoutItem::itemRotation() const
{
return rotation();
return mItemRotation;
}

bool QgsLayoutItem::writeXml( QDomElement &parentElement, QDomDocument &doc, const QgsReadWriteContext &context ) const
Expand Down Expand Up @@ -662,11 +662,19 @@ void QgsLayoutItem::refreshDataDefinedProperty( const QgsLayoutObject::DataDefin
update();
}

void QgsLayoutItem::setItemRotation( const double angle )
void QgsLayoutItem::setItemRotation( double angle, const bool adjustPosition )
{
QPointF itemCenter = positionAtReferencePoint( QgsLayoutItem::Middle );
double rotationRequired = angle - itemRotation();
rotateItem( rotationRequired, itemCenter );
if ( angle >= 360.0 || angle <= -360.0 )
{
angle = std::fmod( angle, 360.0 );
}

QPointF point = adjustPosition ? positionAtReferencePoint( QgsLayoutItem::Middle )
: pos();
double rotationRequired = angle - rotation();
rotateItem( rotationRequired, point );

mItemRotation = angle;
}

void QgsLayoutItem::updateStoredItemPosition()
Expand All @@ -679,19 +687,11 @@ void QgsLayoutItem::rotateItem( const double angle, const QPointF &transformOrig
{
double evaluatedAngle = angle + rotation();
evaluatedAngle = QgsLayoutUtils::normalizedAngle( evaluatedAngle, true );
evaluatedAngle = applyDataDefinedRotation( evaluatedAngle );
mItemRotation = evaluatedAngle;

QPointF itemTransformOrigin = mapFromScene( transformOrigin );
setTransformOriginPoint( itemTransformOrigin );
setRotation( evaluatedAngle );

//adjust stored position of item to match scene pos of reference point
updateStoredItemPosition();

emit rotationChanged( evaluatedAngle );

//update bounds of scene, since rotation may affect this
mLayout->updateBounds();
refreshItemRotation( &itemTransformOrigin );
}


Expand Down Expand Up @@ -825,7 +825,7 @@ bool QgsLayoutItem::writePropertiesToElement( QDomElement &element, QDomDocument
element.setAttribute( QStringLiteral( "referencePoint" ), QString::number( static_cast< int >( mReferencePoint ) ) );
element.setAttribute( QStringLiteral( "position" ), mItemPosition.encodePoint() );
element.setAttribute( QStringLiteral( "size" ), mItemSize.encodeSize() );
element.setAttribute( QStringLiteral( "rotation" ), QString::number( rotation() ) );
element.setAttribute( QStringLiteral( "itemRotation" ), QString::number( mItemRotation ) );
element.setAttribute( QStringLiteral( "groupUuid" ), mParentGroupUuid );

element.setAttribute( "zValue", QString::number( zValue() ) );
Expand Down Expand Up @@ -900,7 +900,7 @@ bool QgsLayoutItem::readPropertiesFromElement( const QDomElement &element, const
mReferencePoint = static_cast< ReferencePoint >( element.attribute( QStringLiteral( "referencePoint" ) ).toInt() );
attemptMove( QgsLayoutPoint::decodePoint( element.attribute( QStringLiteral( "position" ) ) ) );
attemptResize( QgsLayoutSize::decodeSize( element.attribute( QStringLiteral( "size" ) ) ) );
setItemRotation( element.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble() );
setItemRotation( element.attribute( QStringLiteral( "itemRotation" ), QStringLiteral( "0" ) ).toDouble() );

mParentGroupUuid = element.attribute( QStringLiteral( "groupUuid" ) );
if ( !mParentGroupUuid.isEmpty() )
Expand Down Expand Up @@ -1073,9 +1073,43 @@ QSizeF QgsLayoutItem::applyFixedSize( const QSizeF &targetSize )
return targetSize.expandedTo( fixedSizeLayoutUnits );
}

void QgsLayoutItem::refreshItemRotation()
void QgsLayoutItem::refreshItemRotation( QPointF *origin )
{
setItemRotation( itemRotation() );
double r = mItemRotation;

//data defined rotation set?
r = mDataDefinedProperties.valueAsDouble( QgsLayoutItem::ItemRotation, createExpressionContext(), r );

if ( qgsDoubleNear( r, rotation() ) && !origin )
{
return;
}

QPointF transformPoint = origin ? *origin : mapFromScene( positionAtReferencePoint( QgsLayoutItem::Middle ) );

if ( !transformPoint.isNull() )
{
//adjustPosition set, so shift the position of the item so that rotation occurs around item center
//create a line from the transform point to the item's origin, in scene coordinates
QLineF refLine = QLineF( mapToScene( transformPoint ), mapToScene( QPointF( 0, 0 ) ) );
//rotate this line by the current rotation angle
refLine.setAngle( refLine.angle() - r + rotation() );
//get new end point of line - this is the new item position
QPointF rotatedReferencePoint = refLine.p2();
setPos( rotatedReferencePoint );
}

setTransformOriginPoint( 0, 0 );
QGraphicsItem::setRotation( r );

//adjust stored position of item to match scene pos of reference point
updateStoredItemPosition();
emit sizePositionChanged();

emit rotationChanged( r );

//update bounds of scene, since rotation may affect this
mLayout->updateBounds();
}

void QgsLayoutItem::refreshOpacity( bool updateItem )
Expand Down
21 changes: 17 additions & 4 deletions src/core/layout/qgslayoutitem.h
Expand Up @@ -289,9 +289,14 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt

/**
* Returns the current rotation for the item, in degrees clockwise.
*
* Note that this method will always return the user-set rotation for the item,
* which may differ from the current item rotation (if data defined rotation
* settings are present). Use QGraphicsItem::rotation() to obtain the current
* item rotation.
*
* \see setItemRotation()
*/
//TODO
double itemRotation() const;

/**
Expand Down Expand Up @@ -504,11 +509,15 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt
virtual void refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property = QgsLayoutObject::AllProperties );

/**
* Sets the layout item's \a rotation, in degrees clockwise. This rotation occurs around the center of the item.
* Sets the layout item's \a rotation, in degrees clockwise.
*
* If \a adjustPosition is true, then this rotation occurs around the center of the item.
* If \a adjustPosition is false, rotation occurs around the item origin.
*
* \see itemRotation()
* \see rotateItem()
*/
virtual void setItemRotation( const double rotation );
virtual void setItemRotation( double rotation, bool adjustPosition = true );

/**
* Rotates the item by a specified \a angle in degrees clockwise around a specified reference point.
Expand Down Expand Up @@ -602,10 +611,14 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt
/**
* Refreshes an item's rotation by rechecking it against any possible overrides
* such as data defined rotation.
*
* The optional \a origin point specifies the origin (in item coordinates)
* around which the rotation should be applied.
*
* \see refreshItemSize()
* \see refreshItemPosition()
*/
void refreshItemRotation();
void refreshItemRotation( QPointF *origin = nullptr );

/**
* Refresh item's opacity, considering data defined opacity.
Expand Down
6 changes: 3 additions & 3 deletions src/core/layout/qgslayoutitemgroup.cpp
Expand Up @@ -310,14 +310,14 @@ void QgsLayoutItemGroup::updateBoundingRect( QgsLayoutItem *item )
mBoundingRectangle = QRectF( 0, 0, item->rect().width(), item->rect().height() );
setSceneRect( QRectF( item->pos().x(), item->pos().y(), item->rect().width(), item->rect().height() ) );

if ( !qgsDoubleNear( item->itemRotation(), 0.0 ) )
if ( !qgsDoubleNear( item->rotation(), 0.0 ) )
{
setItemRotation( item->itemRotation() );
setItemRotation( item->rotation() );
}
}
else
{
if ( !qgsDoubleNear( item->itemRotation(), itemRotation() ) )
if ( !qgsDoubleNear( item->rotation(), rotation() ) )
{
//items have mixed rotation, so reset rotation of group
mBoundingRectangle = mapRectToScene( mBoundingRectangle );
Expand Down
7 changes: 1 addition & 6 deletions src/gui/layout/qgslayoutitemwidget.cpp
Expand Up @@ -642,10 +642,7 @@ void QgsLayoutItemPropertiesWidget::setValuesForGuiNonPositionElements()
mBackgroundGroupBox->setChecked( mItem->hasBackground() );
mBlendModeCombo->setBlendMode( mItem->blendMode() );
mOpacityWidget->setOpacity( mItem->itemOpacity() );

#if 0//TODO
mItemRotationSpinBox->setValue( mItem->itemRotation( QgsComposerObject::OriginalValue ) );
#endif
mItemRotationSpinBox->setValue( mItem->itemRotation() );
mExcludeFromPrintsCheckBox->setChecked( mItem->excludeFromExports() );

block( false );
Expand Down Expand Up @@ -875,9 +872,7 @@ void QgsLayoutItemPropertiesWidget::mItemRotationSpinBox_valueChanged( double va
if ( mItem )
{
mItem->layout()->undoStack()->beginCommand( mItem, tr( "Rotate" ), QgsLayoutItem::UndoRotation );
#if 0 //TODO
mItem->setItemRotation( val, true );
#endif
mItem->update();
mItem->layout()->undoStack()->endCommand();
}
Expand Down
4 changes: 2 additions & 2 deletions src/gui/layout/qgslayoutmousehandles.cpp
Expand Up @@ -295,12 +295,12 @@ bool QgsLayoutMouseHandles::selectionRotation( double &rotation ) const
auto itemIter = selectedItems.constBegin();

//start with rotation of first selected item
double firstItemRotation = ( *itemIter )->itemRotation();
double firstItemRotation = ( *itemIter )->rotation();

//iterate through remaining items, checking if they have same rotation
for ( ++itemIter; itemIter != selectedItems.end(); ++itemIter )
{
if ( !qgsDoubleNear( ( *itemIter )->itemRotation(), firstItemRotation ) )
if ( !qgsDoubleNear( ( *itemIter )->rotation(), firstItemRotation ) )
{
//item has a different rotation, so return false
return false;
Expand Down
3 changes: 2 additions & 1 deletion src/ui/layout/qgslayoutdesignerbase.ui
Expand Up @@ -83,7 +83,7 @@
<x>0</x>
<y>0</y>
<width>1083</width>
<height>42</height>
<height>25</height>
</rect>
</property>
<widget class="QMenu" name="mLayoutMenu">
Expand Down Expand Up @@ -1037,6 +1037,7 @@
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
</resources>
<connections/>
</ui>
38 changes: 33 additions & 5 deletions tests/src/core/testqgslayoutitem.cpp
Expand Up @@ -1228,6 +1228,22 @@ void TestQgsLayoutItem::rotation()
QCOMPARE( item->sceneBoundingRect().top(), 8.0 );
QCOMPARE( item->sceneBoundingRect().bottom(), 18.0 );

// set rotation, using top left
std::unique_ptr< TestItem > item2( new TestItem( &l ) );
item2->attemptMove( QgsLayoutPoint( 5.0, 8.0 ) );
item2->attemptResize( QgsLayoutSize( 10.0, 6.0 ) );
item2->setItemRotation( 90, false );
QCOMPARE( item2->positionWithUnits().x(), 5.0 );
QCOMPARE( item2->positionWithUnits().y(), 8.0 );
QCOMPARE( item2->pos().x(), 5.0 );
QCOMPARE( item2->pos().y(), 8.0 );
item2->setItemRotation( 180, true );
QCOMPARE( item2->positionWithUnits().x(), 7.0 );
QCOMPARE( item2->positionWithUnits().y(), 16.0 );
QCOMPARE( item2->pos().x(), 7.0 );
QCOMPARE( item2->pos().y(), 16.0 );


//TODO also changing size?


Expand All @@ -1240,15 +1256,28 @@ void TestQgsLayoutItem::rotation()
item->attemptResize( QgsLayoutSize( 10.0, 6.0 ) );
item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemRotation, QgsProperty::fromExpression( QStringLiteral( "90" ) ) );
item->refreshDataDefinedProperty( QgsLayoutObject::ItemRotation );
QCOMPARE( item->itemRotation(), 90.0 );
QCOMPARE( item->itemRotation(), 0.0 ); // should be unchanged
QCOMPARE( item->rotation(), 90.0 );
QCOMPARE( spyRotationChanged.count(), 6 );
QCOMPARE( spyRotationChanged.at( 5 ).at( 0 ).toDouble(), 90.0 );

// rotation should have applied around item center
QCOMPARE( item->positionWithUnits().x(), 13.0 );
QCOMPARE( item->positionWithUnits().y(), 6.0 );
QCOMPARE( item->pos().x(), 13.0 );
QCOMPARE( item->pos().y(), 6.0 );

//also check when refreshing all properties
item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemRotation, QgsProperty::fromExpression( QStringLiteral( "45" ) ) );
item->dataDefinedProperties().setProperty( QgsLayoutObject::ItemRotation, QgsProperty::fromExpression( QStringLiteral( "180" ) ) );
item->refreshDataDefinedProperty( QgsLayoutObject::AllProperties );
QCOMPARE( item->itemRotation(), 45.0 );
QCOMPARE( item->itemRotation(), 0.0 ); // should be unchanged
QCOMPARE( item->rotation(), 180.0 );
QCOMPARE( spyRotationChanged.count(), 7 );
QCOMPARE( spyRotationChanged.at( 6 ).at( 0 ).toDouble(), 45.0 );
QCOMPARE( spyRotationChanged.at( 6 ).at( 0 ).toDouble(), 180.0 );
QCOMPARE( item->positionWithUnits().x(), 15.0 );
QCOMPARE( item->positionWithUnits().y(), 14.0 );
QCOMPARE( item->pos().x(), 15.0 );
QCOMPARE( item->pos().y(), 14.0 );

delete item;

Expand All @@ -1273,7 +1302,6 @@ void TestQgsLayoutItem::rotation()
}

//TODO rotation tests:
//restoring item from xml respects rotation/position
//rotate item around layout point


Expand Down

0 comments on commit 8629225

Please sign in to comment.