Rework how callouts work with picture item with fixed sizes

When a picture annotation item is set to the fixed size mode,
and has a callout anchor set, always place the annotation itself
at a fixed offset from the callout anchor. This mimics the
behavior of the old SVG annotation decoration.
This commit is contained in:
Nyall Dawson 2024-08-07 14:39:56 +10:00
parent deb506b5db
commit e06b9f243e
8 changed files with 435 additions and 45 deletions

View File

@ -323,6 +323,58 @@ The callout ``anchor`` geometry must be specified in the parent layer's coordina
.. seealso:: :py:func:`calloutAnchor`
.. versionadded:: 3.40
%End
QSizeF offsetFromCallout() const;
%Docstring
Returns the (optional) offset of the annotation item from the :py:func:`~QgsAnnotationItem.calloutAnchor`.
Some annotation item subclasses support placement relative to the callout anchor. For these
items, the offset from callout defines how far (in screen/page units) the item should be
placed from the anchor point.
Units are defined by :py:func:`~QgsAnnotationItem.offsetFromCalloutUnit`
.. seealso:: :py:func:`setOffsetFromCallout`
.. versionadded:: 3.40
%End
void setOffsetFromCallout( const QSizeF &offset );
%Docstring
Sets the offset of the annotation item from the :py:func:`~QgsAnnotationItem.calloutAnchor`.
Some annotation item subclasses support placement relative to the callout anchor. For these
items, the offset from callout defines how far (in screen/page units) the item should be
placed from the anchor point.
Units are defined by :py:func:`~QgsAnnotationItem.offsetFromCalloutUnit`
.. seealso:: :py:func:`offsetFromCallout`
.. versionadded:: 3.40
%End
Qgis::RenderUnit offsetFromCalloutUnit() const;
%Docstring
Returns the units for the :py:func:`~QgsAnnotationItem.offsetFromCallout`.
.. seealso:: :py:func:`offsetFromCallout`
.. seealso:: :py:func:`setOffsetFromCalloutUnit`
.. versionadded:: 3.40
%End
void setOffsetFromCalloutUnit( Qgis::RenderUnit unit );
%Docstring
Sets the ``unit`` for the :py:func:`~QgsAnnotationItem.offsetFromCallout`.
.. seealso:: :py:func:`setOffsetFromCallout`
.. seealso:: :py:func:`offsetFromCalloutUnit`
.. versionadded:: 3.40
%End

View File

@ -323,6 +323,58 @@ The callout ``anchor`` geometry must be specified in the parent layer's coordina
.. seealso:: :py:func:`calloutAnchor`
.. versionadded:: 3.40
%End
QSizeF offsetFromCallout() const;
%Docstring
Returns the (optional) offset of the annotation item from the :py:func:`~QgsAnnotationItem.calloutAnchor`.
Some annotation item subclasses support placement relative to the callout anchor. For these
items, the offset from callout defines how far (in screen/page units) the item should be
placed from the anchor point.
Units are defined by :py:func:`~QgsAnnotationItem.offsetFromCalloutUnit`
.. seealso:: :py:func:`setOffsetFromCallout`
.. versionadded:: 3.40
%End
void setOffsetFromCallout( const QSizeF &offset );
%Docstring
Sets the offset of the annotation item from the :py:func:`~QgsAnnotationItem.calloutAnchor`.
Some annotation item subclasses support placement relative to the callout anchor. For these
items, the offset from callout defines how far (in screen/page units) the item should be
placed from the anchor point.
Units are defined by :py:func:`~QgsAnnotationItem.offsetFromCalloutUnit`
.. seealso:: :py:func:`offsetFromCallout`
.. versionadded:: 3.40
%End
Qgis::RenderUnit offsetFromCalloutUnit() const;
%Docstring
Returns the units for the :py:func:`~QgsAnnotationItem.offsetFromCallout`.
.. seealso:: :py:func:`offsetFromCallout`
.. seealso:: :py:func:`setOffsetFromCalloutUnit`
.. versionadded:: 3.40
%End
void setOffsetFromCalloutUnit( Qgis::RenderUnit unit );
%Docstring
Sets the ``unit`` for the :py:func:`~QgsAnnotationItem.offsetFromCallout`.
.. seealso:: :py:func:`setOffsetFromCallout`
.. seealso:: :py:func:`offsetFromCalloutUnit`
.. versionadded:: 3.40
%End

View File

@ -20,6 +20,8 @@
#include "qgscalloutsregistry.h"
#include "qgsapplication.h"
#include "qgsrendercontext.h"
#include "qgssymbollayerutils.h"
#include "qgsunittypes.h"
QgsAnnotationItem::QgsAnnotationItem() = default;
@ -97,6 +99,8 @@ void QgsAnnotationItem::copyCommonProperties( const QgsAnnotationItem *other )
else
setCallout( nullptr );
setCalloutAnchor( other->calloutAnchor() );
setOffsetFromCallout( other->offsetFromCallout() );
setOffsetFromCalloutUnit( other->offsetFromCalloutUnit() );
}
bool QgsAnnotationItem::writeCommonProperties( QDomElement &element, QDomDocument &doc, const QgsReadWriteContext &context ) const
@ -115,6 +119,11 @@ bool QgsAnnotationItem::writeCommonProperties( QDomElement &element, QDomDocumen
{
element.setAttribute( QStringLiteral( "calloutAnchor" ), mCalloutAnchor.asWkt() );
}
if ( mOffsetFromCallout.isValid() )
{
element.setAttribute( QStringLiteral( "offsetFromCallout" ), QgsSymbolLayerUtils::encodeSize( mOffsetFromCallout ) );
element.setAttribute( QStringLiteral( "offsetFromCalloutUnit" ), QgsUnitTypes::encodeUnit( mOffsetFromCalloutUnit ) );
}
return true;
}
@ -140,6 +149,12 @@ bool QgsAnnotationItem::readCommonProperties( const QDomElement &element, const
const QString calloutAnchorWkt = element.attribute( QStringLiteral( "calloutAnchor" ) );
setCalloutAnchor( calloutAnchorWkt.isEmpty() ? QgsGeometry() : QgsGeometry::fromWkt( calloutAnchorWkt ) );
mOffsetFromCallout = element.attribute( QStringLiteral( "offsetFromCallout" ) ).isEmpty() ? QSizeF() : QgsSymbolLayerUtils::decodeSize( element.attribute( QStringLiteral( "offsetFromCallout" ) ) );
bool ok = false;
mOffsetFromCalloutUnit = QgsUnitTypes::decodeRenderUnit( element.attribute( QStringLiteral( "offsetFromCalloutUnit" ) ), &ok );
if ( !ok )
mOffsetFromCalloutUnit = Qgis::RenderUnit::Millimeters;
return true;
}
@ -167,3 +182,23 @@ void QgsAnnotationItem::renderCallout( QgsRenderContext &context, const QRectF &
mCallout->render( context, rect, angle, anchor, calloutContext );
mCallout->stopRender( context );
}
Qgis::RenderUnit QgsAnnotationItem::offsetFromCalloutUnit() const
{
return mOffsetFromCalloutUnit;
}
void QgsAnnotationItem::setOffsetFromCalloutUnit( Qgis::RenderUnit unit )
{
mOffsetFromCalloutUnit = unit;
}
QSizeF QgsAnnotationItem::offsetFromCallout() const
{
return mOffsetFromCallout;
}
void QgsAnnotationItem::setOffsetFromCallout( const QSizeF &offset )
{
mOffsetFromCallout = offset;
}

View File

@ -334,6 +334,54 @@ class CORE_EXPORT QgsAnnotationItem
*/
void setCalloutAnchor( const QgsGeometry &anchor );
/**
* Returns the (optional) offset of the annotation item from the calloutAnchor().
*
* Some annotation item subclasses support placement relative to the callout anchor. For these
* items, the offset from callout defines how far (in screen/page units) the item should be
* placed from the anchor point.
*
* Units are defined by offsetFromCalloutUnit()
*
* \see setOffsetFromCallout()
* \since QGIS 3.40
*/
QSizeF offsetFromCallout() const;
/**
* Sets the offset of the annotation item from the calloutAnchor().
*
* Some annotation item subclasses support placement relative to the callout anchor. For these
* items, the offset from callout defines how far (in screen/page units) the item should be
* placed from the anchor point.
*
* Units are defined by offsetFromCalloutUnit()
*
* \see offsetFromCallout()
* \since QGIS 3.40
*/
void setOffsetFromCallout( const QSizeF &offset );
/**
* Returns the units for the offsetFromCallout().
*
* \see offsetFromCallout()
* \see setOffsetFromCalloutUnit()
*
* \since QGIS 3.40
*/
Qgis::RenderUnit offsetFromCalloutUnit() const;
/**
* Sets the \a unit for the offsetFromCallout().
*
* \see setOffsetFromCallout()
* \see offsetFromCalloutUnit()
*
* \since QGIS 3.40
*/
void setOffsetFromCalloutUnit( Qgis::RenderUnit unit );
protected:
/**
@ -377,6 +425,8 @@ class CORE_EXPORT QgsAnnotationItem
std::unique_ptr< QgsCallout > mCallout;
QgsGeometry mCalloutAnchor;
QSizeF mOffsetFromCallout;
Qgis::RenderUnit mOffsetFromCalloutUnit = Qgis::RenderUnit::Millimeters;
#ifdef SIP_RUN
QgsAnnotationItem( const QgsAnnotationItem &other );

View File

@ -30,6 +30,8 @@
#include "qgslinesymbollayer.h"
#include "qgsunittypes.h"
#include "qgscalloutsregistry.h"
#include "qgslinestring.h"
#include "qgspolygon.h"
#include <QFileInfo>
@ -96,16 +98,40 @@ void QgsAnnotationPictureItem::render( QgsRenderContext &context, QgsFeedback *f
case Qgis::AnnotationPictureSizeMode::FixedSize:
{
QPointF center = bounds.center().toQPointF();
context.mapToPixel().transformInPlace( center.rx(), center.ry() );
const double widthPixels = context.convertToPainterUnits( mFixedSize.width(), mFixedSizeUnit );
const double heightPixels = context.convertToPainterUnits( mFixedSize.height(), mFixedSizeUnit );
painterBounds = QRectF( center.x() - widthPixels * 0.5,
center.y() - heightPixels * 0.5,
widthPixels, heightPixels );
if ( callout() && !calloutAnchor().isEmpty() )
{
QgsGeometry anchor = calloutAnchor();
const double calloutOffsetWidthPixels = context.convertToPainterUnits( offsetFromCallout().width(), offsetFromCalloutUnit() );
const double calloutOffsetHeightPixels = context.convertToPainterUnits( offsetFromCallout().height(), offsetFromCalloutUnit() );
QPointF anchorPoint = anchor.asQPointF();
if ( context.coordinateTransform().isValid() )
{
double x = anchorPoint.x();
double y = anchorPoint.y();
double z = 0.0;
context.coordinateTransform().transformInPlace( x, y, z );
anchorPoint = QPointF( x, y );
}
context.mapToPixel().transformInPlace( anchorPoint.rx(), anchorPoint.ry() );
painterBounds = QRectF( anchorPoint.x() + calloutOffsetWidthPixels,
anchorPoint.y() + calloutOffsetHeightPixels, widthPixels, heightPixels );
}
else
{
QPointF center = bounds.center().toQPointF();
context.mapToPixel().transformInPlace( center.rx(), center.ry() );
painterBounds = QRectF( center.x() - widthPixels * 0.5,
center.y() - heightPixels * 0.5,
widthPixels, heightPixels );
}
break;
}
}
@ -283,7 +309,7 @@ QList<QgsAnnotationItemNode> QgsAnnotationPictureItem::nodesV2( const QgsAnnotat
BUILTIN_UNREACHABLE
}
Qgis::AnnotationItemEditOperationResult QgsAnnotationPictureItem::applyEditV2( QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext & )
Qgis::AnnotationItemEditOperationResult QgsAnnotationPictureItem::applyEditV2( QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext &context )
{
switch ( operation->type() )
{
@ -352,10 +378,34 @@ Qgis::AnnotationItemEditOperationResult QgsAnnotationPictureItem::applyEditV2( Q
case QgsAbstractAnnotationItemEditOperation::Type::TranslateItem:
{
QgsAnnotationItemEditOperationTranslateItem *moveOperation = qgis::down_cast< QgsAnnotationItemEditOperationTranslateItem * >( operation );
mBounds = QgsRectangle( mBounds.xMinimum() + moveOperation->translationX(),
mBounds.yMinimum() + moveOperation->translationY(),
mBounds.xMaximum() + moveOperation->translationX(),
mBounds.yMaximum() + moveOperation->translationY() );
switch ( mSizeMode )
{
case Qgis::AnnotationPictureSizeMode::SpatialBounds:
mBounds = QgsRectangle( mBounds.xMinimum() + moveOperation->translationX(),
mBounds.yMinimum() + moveOperation->translationY(),
mBounds.xMaximum() + moveOperation->translationX(),
mBounds.yMaximum() + moveOperation->translationY() );
break;
case Qgis::AnnotationPictureSizeMode::FixedSize:
{
if ( callout() && !calloutAnchor().isEmpty() )
{
const double xOffset = context.renderContext().convertFromPainterUnits( moveOperation->translationXPixels(), offsetFromCalloutUnit() );
const double yOffset = context.renderContext().convertFromPainterUnits( moveOperation->translationYPixels(), offsetFromCalloutUnit() );
setOffsetFromCallout( QSizeF( offsetFromCallout().width() + xOffset, offsetFromCallout().height() + yOffset ) );
}
else
{
mBounds = QgsRectangle( mBounds.xMinimum() + moveOperation->translationX(),
mBounds.yMinimum() + moveOperation->translationY(),
mBounds.xMaximum() + moveOperation->translationX(),
mBounds.yMaximum() + moveOperation->translationY() );
}
break;
}
}
return Qgis::AnnotationItemEditOperationResult::Success;
}
@ -435,10 +485,52 @@ QgsAnnotationItemEditOperationTransientResults *QgsAnnotationPictureItem::transi
case Qgis::AnnotationPictureSizeMode::FixedSize:
{
const QgsRectangle currentBounds = context.currentItemBounds();
const QgsRectangle newBounds = QgsRectangle::fromCenterAndSize( mBounds.center() + QgsVector( moveOperation->translationX(), moveOperation->translationY() ),
currentBounds.width(), currentBounds.height() );
return new QgsAnnotationItemEditOperationTransientResults( QgsGeometry::fromRect( newBounds ) );
if ( callout() && !calloutAnchor().isEmpty() )
{
QgsGeometry anchor = calloutAnchor();
const double calloutOffsetWidthPixels = context.renderContext().convertToPainterUnits( offsetFromCallout().width(), offsetFromCalloutUnit() )
+ moveOperation->translationXPixels();
const double calloutOffsetHeightPixels = context.renderContext().convertToPainterUnits( offsetFromCallout().height(), offsetFromCalloutUnit() )
+ moveOperation->translationYPixels();
QPointF anchorPoint = anchor.asQPointF();
if ( context.renderContext().coordinateTransform().isValid() )
{
double x = anchorPoint.x();
double y = anchorPoint.y();
double z = 0.0;
context.renderContext().coordinateTransform().transformInPlace( x, y, z );
anchorPoint = QPointF( x, y );
}
context.renderContext().mapToPixel().transformInPlace( anchorPoint.rx(), anchorPoint.ry() );
const double textOriginXPixels = anchorPoint.x() + calloutOffsetWidthPixels;
const double textOriginYPixels = anchorPoint.y() + calloutOffsetHeightPixels;
const double widthPixels = context.renderContext().convertToPainterUnits( mFixedSize.width(), mFixedSizeUnit );
const double heightPixels = context.renderContext().convertToPainterUnits( mFixedSize.height(), mFixedSizeUnit );
QgsLineString ls( QVector<QgsPointXY> { QgsPointXY( textOriginXPixels, textOriginYPixels ),
QgsPointXY( textOriginXPixels + widthPixels, textOriginYPixels ),
QgsPointXY( textOriginXPixels + widthPixels, textOriginYPixels + heightPixels ),
QgsPointXY( textOriginXPixels, textOriginYPixels + heightPixels ),
QgsPointXY( textOriginXPixels, textOriginYPixels )
} );
QgsGeometry g( new QgsPolygon( ls.clone() ) );
g.transform( context.renderContext().mapToPixel().transform().inverted() );
g.transform( context.renderContext().coordinateTransform(), Qgis::TransformDirection::Reverse );
return new QgsAnnotationItemEditOperationTransientResults( g );
}
else
{
const QgsRectangle currentBounds = context.currentItemBounds();
const QgsRectangle newBounds = QgsRectangle::fromCenterAndSize( mBounds.center() + QgsVector( moveOperation->translationX(), moveOperation->translationY() ),
currentBounds.width(), currentBounds.height() );
return new QgsAnnotationItemEditOperationTransientResults( QgsGeometry::fromRect( newBounds ) );
}
}
}
break;
@ -521,19 +613,26 @@ QgsRectangle QgsAnnotationPictureItem::boundingBox() const
case Qgis::AnnotationPictureSizeMode::SpatialBounds:
{
bounds = mBounds;
if ( callout() && !calloutAnchor().isEmpty() )
{
QgsGeometry anchor = calloutAnchor();
bounds.combineExtentWith( anchor.boundingBox() );
}
break;
}
case Qgis::AnnotationPictureSizeMode::FixedSize:
bounds = QgsRectangle( mBounds.center(), mBounds.center() );
if ( callout() && !calloutAnchor().isEmpty() )
{
bounds = calloutAnchor().boundingBox();
}
else
{
bounds = QgsRectangle( mBounds.center(), mBounds.center() );
}
break;
}
if ( callout() && !calloutAnchor().isEmpty() )
{
QgsGeometry anchor = calloutAnchor();
bounds.combineExtentWith( anchor.boundingBox() );
}
return bounds;
}
@ -546,24 +645,55 @@ QgsRectangle QgsAnnotationPictureItem::boundingBox( QgsRenderContext &context )
case Qgis::AnnotationPictureSizeMode::FixedSize:
{
QPointF center = mBounds.center().toQPointF();
if ( context.coordinateTransform().isValid() )
{
double x = center.x();
double y = center.y();
double z = 0.0;
context.coordinateTransform().transformInPlace( x, y, z );
center = QPointF( x, y );
}
context.mapToPixel().transformInPlace( center.rx(), center.ry() );
const double widthPixels = context.convertToPainterUnits( mFixedSize.width(), mFixedSizeUnit );
const double heightPixels = context.convertToPainterUnits( mFixedSize.height(), mFixedSizeUnit );
const QRectF boundsInPixels( center.x() - widthPixels * 0.5,
center.y() - heightPixels * 0.5,
widthPixels, heightPixels );
QRectF boundsInPixels;
if ( callout() && !calloutAnchor().isEmpty() )
{
QgsGeometry anchor = calloutAnchor();
const double calloutOffsetWidthPixels = context.convertToPainterUnits( offsetFromCallout().width(), offsetFromCalloutUnit() );
const double calloutOffsetHeightPixels = context.convertToPainterUnits( offsetFromCallout().height(), offsetFromCalloutUnit() );
QPointF anchorPoint = anchor.asQPointF();
if ( context.coordinateTransform().isValid() )
{
double x = anchorPoint.x();
double y = anchorPoint.y();
double z = 0.0;
context.coordinateTransform().transformInPlace( x, y, z );
anchorPoint = QPointF( x, y );
}
context.mapToPixel().transformInPlace( anchorPoint.rx(), anchorPoint.ry() );
QgsRectangle textRect( anchorPoint.x() + calloutOffsetWidthPixels,
anchorPoint.y() + calloutOffsetHeightPixels,
anchorPoint.x() + calloutOffsetWidthPixels + widthPixels,
anchorPoint.y() + calloutOffsetHeightPixels + heightPixels );
QgsRectangle anchorRect( anchorPoint.x(), anchorPoint.y(), anchorPoint.x(), anchorPoint.y() );
anchorRect.combineExtentWith( textRect );
boundsInPixels = anchorRect.toRectF();
}
else
{
QPointF center = mBounds.center().toQPointF();
if ( context.coordinateTransform().isValid() )
{
double x = center.x();
double y = center.y();
double z = 0.0;
context.coordinateTransform().transformInPlace( x, y, z );
center = QPointF( x, y );
}
context.mapToPixel().transformInPlace( center.rx(), center.ry() );
boundsInPixels = QRectF( center.x() - widthPixels * 0.5,
center.y() - heightPixels * 0.5,
widthPixels, heightPixels );
}
const QgsPointXY topLeft = context.mapToPixel().toMapCoordinates( boundsInPixels.left(), boundsInPixels.top() );
const QgsPointXY topRight = context.mapToPixel().toMapCoordinates( boundsInPixels.right(), boundsInPixels.top() );
const QgsPointXY bottomLeft = context.mapToPixel().toMapCoordinates( boundsInPixels.left(), boundsInPixels.bottom() );
@ -571,12 +701,6 @@ QgsRectangle QgsAnnotationPictureItem::boundingBox( QgsRenderContext &context )
const QgsRectangle boundsMapUnits = QgsRectangle( topLeft.x(), bottomLeft.y(), bottomRight.x(), topRight.y() );
QgsRectangle textRect = context.coordinateTransform().transformBoundingBox( boundsMapUnits, Qgis::TransformDirection::Reverse );
if ( callout() && !calloutAnchor().isEmpty() )
{
QgsGeometry anchor = calloutAnchor();
textRect.combineExtentWith( anchor.boundingBox() );
}
return textRect;
}
}

View File

@ -378,6 +378,7 @@ void QgsMapToolModifyAnnotation::canvasDoubleClickEvent( QgsMapMouseEvent *event
QgsAnnotationItemEditContext context;
context.setCurrentItemBounds( toLayerCoordinates( layer, mSelectedItemBounds ) );
context.setRenderContext( QgsRenderContext::fromMapSettings( canvas()->mapSettings() ) );
switch ( layer->applyEditV2( &operation, context ) )
{
case Qgis::AnnotationItemEditOperationResult::Success:

View File

@ -74,6 +74,9 @@ class TestQgsAnnotationPictureItem(QgisTestCase):
item.setFixedSize(QSizeF(56,
57))
item.setFixedSizeUnit(Qgis.RenderUnit.Inches)
item.setOffsetFromCallout(QSizeF(13.6, 17.2))
item.setOffsetFromCalloutUnit(Qgis.RenderUnit.Inches)
self.assertEqual(item.bounds().toString(3), '100.000,200.000 : 300.000,400.000')
self.assertEqual(item.path(), self.get_test_data_path('sample_svg.svg').as_posix())
self.assertEqual(item.format(), Qgis.PictureFormat.SVG)
@ -86,6 +89,8 @@ class TestQgsAnnotationPictureItem(QgisTestCase):
self.assertEqual(item.fixedSize(), QSizeF(56,
57))
self.assertEqual(item.fixedSizeUnit(), Qgis.RenderUnit.Inches)
self.assertEqual(item.offsetFromCallout(), QSizeF(13.6, 17.2))
self.assertEqual(item.offsetFromCalloutUnit(), Qgis.RenderUnit.Inches)
item.setBackgroundSymbol(QgsFillSymbol.createSimple({'color': '200,100,100', 'outline_color': 'black'}))
item.setFrameSymbol(QgsFillSymbol.createSimple(
@ -141,10 +146,38 @@ class TestQgsAnnotationPictureItem(QgisTestCase):
item.setSizeMode(Qgis.AnnotationPictureSizeMode.FixedSize)
self.assertEqual(item.bounds().toString(3), '10.000,20.000 : 30.000,40.000')
context = QgsAnnotationItemEditContext()
render_context = QgsRenderContext()
render_context.setScaleFactor(5)
context.setRenderContext(render_context)
self.assertEqual(item.applyEditV2(QgsAnnotationItemEditOperationTranslateItem('', 100, 200),
QgsAnnotationItemEditContext()),
context),
Qgis.AnnotationItemEditOperationResult.Success)
self.assertEqual(item.bounds().toString(3), '110.000,220.000 : 130.000,240.000')
self.assertEqual(item.offsetFromCallout(), QSizeF())
def test_translate_fixed_size_with_callout_anchor(self):
item = QgsAnnotationPictureItem(Qgis.PictureFormat.Raster,
self.get_test_data_path(
'rgb256x256.png').as_posix(),
QgsRectangle(10, 20, 30, 40))
item.setCalloutAnchor(QgsGeometry.fromWkt('Point(1 3)'))
item.setCallout(QgsBalloonCallout())
item.setSizeMode(Qgis.AnnotationPictureSizeMode.FixedSize)
self.assertEqual(item.bounds().toString(3), '10.000,20.000 : 30.000,40.000')
context = QgsAnnotationItemEditContext()
render_context = QgsRenderContext()
render_context.setScaleFactor(5)
context.setRenderContext(render_context)
self.assertEqual(item.applyEditV2(QgsAnnotationItemEditOperationTranslateItem('', 100, 200, 50, 30),
context),
Qgis.AnnotationItemEditOperationResult.Success)
# should affect callout offset only
self.assertEqual(item.offsetFromCallout(), QSizeF(9, 5))
self.assertEqual(item.offsetFromCalloutUnit(), Qgis.RenderUnit.Millimeters)
def test_apply_move_node_edit_spatial_bounds(self):
item = QgsAnnotationPictureItem(Qgis.PictureFormat.Raster,
@ -189,9 +222,14 @@ class TestQgsAnnotationPictureItem(QgisTestCase):
item.setSizeMode(Qgis.AnnotationPictureSizeMode.FixedSize)
self.assertEqual(item.bounds().toString(3), '10.000,20.000 : 30.000,40.000')
context = QgsAnnotationItemEditContext()
render_context = QgsRenderContext()
render_context.setScaleFactor(5)
context.setRenderContext(render_context)
self.assertEqual(item.applyEditV2(
QgsAnnotationItemEditOperationMoveNode('', QgsVertexId(0, 0, 0), QgsPoint(30, 20), QgsPoint(17, 18)),
QgsAnnotationItemEditContext()),
context),
Qgis.AnnotationItemEditOperationResult.Success)
self.assertEqual(item.bounds().toString(3), '7.000,8.000 : 27.000,28.000')
@ -286,10 +324,38 @@ class TestQgsAnnotationPictureItem(QgisTestCase):
op = QgsAnnotationItemEditOperationTranslateItem('', 100, 200)
context = QgsAnnotationItemEditContext()
context.setCurrentItemBounds(QgsRectangle(1, 2, 3, 4))
render_context = QgsRenderContext()
render_context.setScaleFactor(5)
context.setRenderContext(render_context)
res = item.transientEditResultsV2(op, context)
self.assertEqual(res.representativeGeometry().asWkt(),
'Polygon ((119 229, 121 229, 121 231, 119 231, 119 229))')
def test_transient_translate_operation_fixed_size_with_callout_anchor(self):
item = QgsAnnotationPictureItem(Qgis.PictureFormat.Raster,
self.get_test_data_path(
'rgb256x256.png').as_posix(),
QgsRectangle(10, 20, 30, 40))
item.setSizeMode(Qgis.AnnotationPictureSizeMode.FixedSize)
item.setFixedSize(QSizeF(56,
57))
item.setFixedSizeUnit(Qgis.RenderUnit.Inches)
item.setCalloutAnchor(QgsGeometry.fromWkt('Point(1 3)'))
item.setCallout(QgsBalloonCallout())
self.assertEqual(item.bounds().toString(3), '10.000,20.000 : 30.000,40.000')
op = QgsAnnotationItemEditOperationTranslateItem('', 100, 200, 50, 30)
context = QgsAnnotationItemEditContext()
context.setCurrentItemBounds(QgsRectangle(1, 2, 3, 4))
render_context = QgsRenderContext()
render_context.setScaleFactor(0.5)
context.setRenderContext(render_context)
res = item.transientEditResultsV2(op, context)
self.assertEqual(res.representativeGeometry().asWkt(2),
'Polygon ((50.5 -26.5, 761.7 -26.5, 761.7 -750.4, 50.5 -750.4, 50.5 -26.5))')
def testReadWriteXml(self):
doc = QDomDocument("testdoc")
elem = doc.createElement('test')
@ -308,6 +374,8 @@ class TestQgsAnnotationPictureItem(QgisTestCase):
item.setFixedSizeUnit(Qgis.RenderUnit.Inches)
item.setCalloutAnchor(QgsGeometry.fromWkt('Point(1 3)'))
item.setCallout(QgsBalloonCallout())
item.setOffsetFromCallout(QSizeF(13.6, 17.2))
item.setOffsetFromCalloutUnit(Qgis.RenderUnit.Inches)
self.assertTrue(item.writeXml(elem, doc, QgsReadWriteContext()))
@ -331,6 +399,8 @@ class TestQgsAnnotationPictureItem(QgisTestCase):
self.assertEqual(s2.fixedSizeUnit(), Qgis.RenderUnit.Inches)
self.assertEqual(s2.calloutAnchor().asWkt(), 'Point (1 3)')
self.assertIsInstance(s2.callout(), QgsBalloonCallout)
self.assertEqual(s2.offsetFromCallout(), QSizeF(13.6, 17.2))
self.assertEqual(s2.offsetFromCalloutUnit(), Qgis.RenderUnit.Inches)
def testClone(self):
item = QgsAnnotationPictureItem(Qgis.PictureFormat.Raster, self.get_test_data_path('rgb256x256.png').as_posix(),
@ -347,6 +417,8 @@ class TestQgsAnnotationPictureItem(QgisTestCase):
item.setFixedSizeUnit(Qgis.RenderUnit.Inches)
item.setCalloutAnchor(QgsGeometry.fromWkt('Point(1 3)'))
item.setCallout(QgsBalloonCallout())
item.setOffsetFromCallout(QSizeF(13.6, 17.2))
item.setOffsetFromCalloutUnit(Qgis.RenderUnit.Inches)
s2 = item.clone()
self.assertEqual(s2.bounds().toString(3), '10.000,20.000 : 30.000,40.000')
@ -366,6 +438,8 @@ class TestQgsAnnotationPictureItem(QgisTestCase):
self.assertEqual(s2.fixedSizeUnit(), Qgis.RenderUnit.Inches)
self.assertEqual(s2.calloutAnchor().asWkt(), 'Point (1 3)')
self.assertIsInstance(s2.callout(), QgsBalloonCallout)
self.assertEqual(s2.offsetFromCallout(), QSizeF(13.6, 17.2))
self.assertEqual(s2.offsetFromCalloutUnit(), Qgis.RenderUnit.Inches)
def testRenderRasterLockedAspect(self):
item = QgsAnnotationPictureItem(Qgis.PictureFormat.Raster, self.get_test_data_path('rgb256x256.png').as_posix(),
@ -580,6 +654,8 @@ class TestQgsAnnotationPictureItem(QgisTestCase):
callout.lineSymbol().setWidth(1)
item.setCallout(callout)
item.setCalloutAnchor(QgsGeometry.fromWkt('Point(11 12)'))
item.setOffsetFromCallout(QSizeF(60, -80))
item.setOffsetFromCalloutUnit(Qgis.RenderUnit.Points)
settings = QgsMapSettings()
settings.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:4326'))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 877 B

After

Width:  |  Height:  |  Size: 857 B