Fix incorrect annotation scaling when exporting layouts
Previously, annotation size and position always used pixel units. This did not work well when exporting layouts, resulting in tiny annotations (it also caused issues when moving projects between hidpi/non hidpi displays). Instead, use millimeters for annotation size and position so that the appearance is consistent across displays and works correctly in layout exports. Add lots of unit tests covering this. Fixes #18373
@ -143,34 +143,80 @@ the relative percentage for the position compared to the map width and height.
|
||||
.. seealso:: :py:func:`relativePosition`
|
||||
%End
|
||||
|
||||
void setFrameOffsetFromReferencePoint( QPointF offset );
|
||||
void setFrameOffsetFromReferencePoint( QPointF offset ) /Deprecated/;
|
||||
%Docstring
|
||||
Sets the annotation's frame's offset from the mapPosition() reference point.
|
||||
Sets the annotation's frame's offset (in pixels) from the mapPosition() reference point.
|
||||
|
||||
.. seealso:: :py:func:`frameOffsetFromReferencePoint`
|
||||
|
||||
.. deprecated:: use setFrameOffsetFromReferencePointMm() instead
|
||||
%End
|
||||
|
||||
QPointF frameOffsetFromReferencePoint() const;
|
||||
QPointF frameOffsetFromReferencePoint() const /Deprecated/;
|
||||
%Docstring
|
||||
Returns the annotation's frame's offset from the mapPosition() reference point.
|
||||
Returns the annotation's frame's offset (in pixels) from the mapPosition() reference point.
|
||||
|
||||
.. seealso:: :py:func:`setFrameOffsetFromReferencePoint`
|
||||
|
||||
.. deprecated:: use frameOffsetFromReferencePointMm() instead
|
||||
%End
|
||||
|
||||
void setFrameSize( QSizeF size );
|
||||
void setFrameOffsetFromReferencePointMm( QPointF offset );
|
||||
%Docstring
|
||||
Sets the size of the annotation's frame (the main area in which
|
||||
Sets the annotation's frame's offset (in millimeters) from the mapPosition() reference point.
|
||||
|
||||
.. seealso:: :py:func:`frameOffsetFromReferencePointMm`
|
||||
|
||||
.. versionadded:: 3.4.8
|
||||
%End
|
||||
|
||||
QPointF frameOffsetFromReferencePointMm() const;
|
||||
%Docstring
|
||||
Returns the annotation's frame's offset (in millimeters) from the mapPosition() reference point.
|
||||
|
||||
.. seealso:: :py:func:`setFrameOffsetFromReferencePointMm`
|
||||
|
||||
.. versionadded:: 3.4.8
|
||||
%End
|
||||
|
||||
void setFrameSize( QSizeF size ) /Deprecated/;
|
||||
%Docstring
|
||||
Sets the size (in pixels) of the annotation's frame (the main area in which
|
||||
the annotation's content is drawn).
|
||||
|
||||
.. seealso:: :py:func:`frameSize`
|
||||
|
||||
.. deprecated:: use setFrameSizeMm() instead
|
||||
%End
|
||||
|
||||
QSizeF frameSize() const;
|
||||
QSizeF frameSize() const /Deprecated/;
|
||||
%Docstring
|
||||
Returns the size of the annotation's frame (the main area in which
|
||||
Returns the size (in pixels) of the annotation's frame (the main area in which
|
||||
the annotation's content is drawn).
|
||||
|
||||
.. seealso:: :py:func:`setFrameSize`
|
||||
|
||||
.. deprecated:: use frameSizeMm() instead
|
||||
%End
|
||||
|
||||
void setFrameSizeMm( QSizeF size );
|
||||
%Docstring
|
||||
Sets the size (in millimeters) of the annotation's frame (the main area in which
|
||||
the annotation's content is drawn).
|
||||
|
||||
.. seealso:: :py:func:`frameSizeMm`
|
||||
|
||||
.. versionadded:: 3.4.8
|
||||
%End
|
||||
|
||||
QSizeF frameSizeMm() const;
|
||||
%Docstring
|
||||
Returns the size (in millimeters) of the annotation's frame (the main area in which
|
||||
the annotation's content is drawn).
|
||||
|
||||
.. seealso:: :py:func:`setFrameSizeMm`
|
||||
|
||||
.. versionadded:: 3.4.8
|
||||
%End
|
||||
|
||||
void setContentsMargin( const QgsMargins &margins );
|
||||
|
@ -50,26 +50,19 @@ QDialog *QgsMapToolAnnotation::createItemEditor( QgsMapCanvasAnnotationItem *ite
|
||||
|
||||
QgsAnnotation *annotation = item->annotation();
|
||||
|
||||
QgsTextAnnotation *tItem = dynamic_cast<QgsTextAnnotation *>( annotation );
|
||||
if ( tItem )
|
||||
if ( qobject_cast<QgsTextAnnotation *>( annotation ) )
|
||||
{
|
||||
return new QgsTextAnnotationDialog( item );
|
||||
}
|
||||
|
||||
QgsFormAnnotation *fItem = dynamic_cast<QgsFormAnnotation *>( annotation );
|
||||
if ( fItem )
|
||||
else if ( qobject_cast<QgsFormAnnotation *>( annotation ) )
|
||||
{
|
||||
return new QgsFormAnnotationDialog( item );
|
||||
}
|
||||
|
||||
QgsHtmlAnnotation *hItem = dynamic_cast<QgsHtmlAnnotation *>( annotation );
|
||||
if ( hItem )
|
||||
else if ( qobject_cast<QgsHtmlAnnotation *>( annotation ) )
|
||||
{
|
||||
return new QgsHtmlAnnotationDialog( item );
|
||||
}
|
||||
|
||||
QgsSvgAnnotation *sItem = dynamic_cast<QgsSvgAnnotation *>( annotation );
|
||||
if ( sItem )
|
||||
else if ( qobject_cast<QgsSvgAnnotation *>( annotation ) )
|
||||
{
|
||||
return new QgsSvgAnnotationDialog( item );
|
||||
}
|
||||
@ -124,7 +117,7 @@ void QgsMapToolAnnotation::canvasPressEvent( QgsMapMouseEvent *e )
|
||||
annotation->setMapPositionCrs( mCanvas->mapSettings().destinationCrs() );
|
||||
annotation->setRelativePosition( QPointF( e->pos().x() / mCanvas->width(),
|
||||
e->pos().y() / mCanvas->height() ) );
|
||||
annotation->setFrameSize( QSizeF( 200, 100 ) );
|
||||
annotation->setFrameSizeMm( QSizeF( 50, 25 ) );
|
||||
|
||||
QgsProject::instance()->annotationManager()->addAnnotation( annotation );
|
||||
|
||||
@ -193,7 +186,11 @@ void QgsMapToolAnnotation::canvasMoveEvent( QgsMapMouseEvent *e )
|
||||
QPointF newCanvasPos = item->pos() + ( e->pos() - mLastMousePosition );
|
||||
if ( annotation->hasFixedMapPosition() )
|
||||
{
|
||||
annotation->setFrameOffsetFromReferencePoint( annotation->frameOffsetFromReferencePoint() + ( e->pos() - mLastMousePosition ) );
|
||||
const double pixelToMmScale = 25.4 / mCanvas->logicalDpiX();
|
||||
const double deltaX = pixelToMmScale * ( e->pos().x() - mLastMousePosition.x() );
|
||||
const double deltaY = pixelToMmScale * ( e->pos().y() - mLastMousePosition.y() );
|
||||
annotation->setFrameOffsetFromReferencePointMm( QPointF( annotation->frameOffsetFromReferencePointMm().x() + deltaX,
|
||||
annotation->frameOffsetFromReferencePointMm().y() + deltaY ) );
|
||||
annotation->setRelativePosition( QPointF( newCanvasPos.x() / mCanvas->width(),
|
||||
newCanvasPos.y() / mCanvas->height() ) );
|
||||
}
|
||||
@ -210,9 +207,12 @@ void QgsMapToolAnnotation::canvasMoveEvent( QgsMapMouseEvent *e )
|
||||
else if ( mCurrentMoveAction != QgsMapCanvasAnnotationItem::NoAction )
|
||||
{
|
||||
//handle the frame resize actions
|
||||
QSizeF size = annotation->frameSize();
|
||||
double xmin = annotation->frameOffsetFromReferencePoint().x();
|
||||
double ymin = annotation->frameOffsetFromReferencePoint().y();
|
||||
|
||||
const double pixelToMmScale = 25.4 / mCanvas->logicalDpiX();
|
||||
|
||||
QSizeF size = annotation->frameSizeMm();
|
||||
double xmin = annotation->frameOffsetFromReferencePointMm().x();
|
||||
double ymin = annotation->frameOffsetFromReferencePointMm().y();
|
||||
double xmax = xmin + size.width();
|
||||
double ymax = ymin + size.height();
|
||||
double relPosX = annotation->relativePosition().x();
|
||||
@ -222,27 +222,27 @@ void QgsMapToolAnnotation::canvasMoveEvent( QgsMapMouseEvent *e )
|
||||
mCurrentMoveAction == QgsMapCanvasAnnotationItem::ResizeFrameRightDown ||
|
||||
mCurrentMoveAction == QgsMapCanvasAnnotationItem::ResizeFrameRightUp )
|
||||
{
|
||||
xmax += e->pos().x() - mLastMousePosition.x();
|
||||
xmax += pixelToMmScale * ( e->pos().x() - mLastMousePosition.x() );
|
||||
}
|
||||
if ( mCurrentMoveAction == QgsMapCanvasAnnotationItem::ResizeFrameLeft ||
|
||||
mCurrentMoveAction == QgsMapCanvasAnnotationItem::ResizeFrameLeftDown ||
|
||||
mCurrentMoveAction == QgsMapCanvasAnnotationItem::ResizeFrameLeftUp )
|
||||
{
|
||||
xmin += e->pos().x() - mLastMousePosition.x();
|
||||
xmin += pixelToMmScale * ( e->pos().x() - mLastMousePosition.x() );
|
||||
relPosX = ( relPosX * mCanvas->width() + e->pos().x() - mLastMousePosition.x() ) / static_cast<double>( mCanvas->width() );
|
||||
}
|
||||
if ( mCurrentMoveAction == QgsMapCanvasAnnotationItem::ResizeFrameUp ||
|
||||
mCurrentMoveAction == QgsMapCanvasAnnotationItem::ResizeFrameLeftUp ||
|
||||
mCurrentMoveAction == QgsMapCanvasAnnotationItem::ResizeFrameRightUp )
|
||||
{
|
||||
ymin += e->pos().y() - mLastMousePosition.y();
|
||||
ymin += pixelToMmScale * ( e->pos().y() - mLastMousePosition.y() );
|
||||
relPosY = ( relPosY * mCanvas->height() + e->pos().y() - mLastMousePosition.y() ) / static_cast<double>( mCanvas->height() );
|
||||
}
|
||||
if ( mCurrentMoveAction == QgsMapCanvasAnnotationItem::ResizeFrameDown ||
|
||||
mCurrentMoveAction == QgsMapCanvasAnnotationItem::ResizeFrameLeftDown ||
|
||||
mCurrentMoveAction == QgsMapCanvasAnnotationItem::ResizeFrameRightDown )
|
||||
{
|
||||
ymax += e->pos().y() - mLastMousePosition.y();
|
||||
ymax += pixelToMmScale * ( e->pos().y() - mLastMousePosition.y() );
|
||||
}
|
||||
|
||||
//switch min / max if necessary
|
||||
@ -260,8 +260,8 @@ void QgsMapToolAnnotation::canvasMoveEvent( QgsMapMouseEvent *e )
|
||||
ymin = tmp;
|
||||
}
|
||||
|
||||
annotation->setFrameOffsetFromReferencePoint( QPointF( xmin, ymin ) );
|
||||
annotation->setFrameSize( QSizeF( xmax - xmin, ymax - ymin ) );
|
||||
annotation->setFrameOffsetFromReferencePointMm( QPointF( xmin, ymin ) );
|
||||
annotation->setFrameSizeMm( QSizeF( xmax - xmin, ymax - ymin ) );
|
||||
annotation->setRelativePosition( QPointF( relPosX, relPosY ) );
|
||||
item->update();
|
||||
QgsProject::instance()->setDirty( true );
|
||||
@ -351,7 +351,7 @@ void QgsMapToolAnnotation::toggleTextItemVisibilities()
|
||||
const auto constItemList = itemList;
|
||||
for ( QgsMapCanvasAnnotationItem *item : constItemList )
|
||||
{
|
||||
QgsTextAnnotation *textItem = dynamic_cast<QgsTextAnnotation *>( item->annotation() );
|
||||
QgsTextAnnotation *textItem = qobject_cast<QgsTextAnnotation *>( item->annotation() );
|
||||
if ( textItem )
|
||||
{
|
||||
textItem->setVisible( !textItem->isVisible() );
|
||||
|
@ -19,9 +19,12 @@
|
||||
#include "qgssymbollayerutils.h"
|
||||
#include "qgsmaplayer.h"
|
||||
#include "qgsproject.h"
|
||||
#include "qgsgeometryutils.h"
|
||||
#include <QPen>
|
||||
#include <QPainter>
|
||||
|
||||
Q_GUI_EXPORT extern int qt_defaultDpiX();
|
||||
|
||||
QgsAnnotation::QgsAnnotation( QObject *parent )
|
||||
: QObject( parent )
|
||||
, mMarkerSymbol( new QgsMarkerSymbol() )
|
||||
@ -74,14 +77,37 @@ void QgsAnnotation::setRelativePosition( QPointF position )
|
||||
}
|
||||
|
||||
void QgsAnnotation::setFrameOffsetFromReferencePoint( QPointF offset )
|
||||
{
|
||||
// convert from offset in pixels at 96 dpi to mm
|
||||
setFrameOffsetFromReferencePointMm( offset / 3.7795275 );
|
||||
}
|
||||
|
||||
QPointF QgsAnnotation::frameOffsetFromReferencePoint() const
|
||||
{
|
||||
return mOffsetFromReferencePoint / 3.7795275;
|
||||
}
|
||||
|
||||
void QgsAnnotation::setFrameOffsetFromReferencePointMm( QPointF offset )
|
||||
{
|
||||
mOffsetFromReferencePoint = offset;
|
||||
|
||||
updateBalloon();
|
||||
emit moved();
|
||||
emit appearanceChanged();
|
||||
}
|
||||
|
||||
void QgsAnnotation::setFrameSize( QSizeF size )
|
||||
{
|
||||
// convert from size in pixels at 96 dpi to mm
|
||||
setFrameSizeMm( size / 3.7795275 );
|
||||
}
|
||||
|
||||
QSizeF QgsAnnotation::frameSize() const
|
||||
{
|
||||
return mFrameSize / 3.7795275;
|
||||
}
|
||||
|
||||
void QgsAnnotation::setFrameSizeMm( QSizeF size )
|
||||
{
|
||||
QSizeF frameSize = minimumFrameSize().expandedTo( size ); //don't allow frame sizes below minimum
|
||||
mFrameSize = frameSize;
|
||||
@ -118,16 +144,20 @@ void QgsAnnotation::render( QgsRenderContext &context ) const
|
||||
}
|
||||
if ( mHasFixedMapPosition )
|
||||
{
|
||||
painter->translate( mOffsetFromReferencePoint.x() + context.convertToPainterUnits( mContentsMargins.left(), QgsUnitTypes::RenderMillimeters ),
|
||||
mOffsetFromReferencePoint.y() + context.convertToPainterUnits( mContentsMargins.top(), QgsUnitTypes::RenderMillimeters ) );
|
||||
painter->translate( context.convertToPainterUnits( mOffsetFromReferencePoint.x(), QgsUnitTypes::RenderMillimeters ) + context.convertToPainterUnits( mContentsMargins.left(), QgsUnitTypes::RenderMillimeters ),
|
||||
context.convertToPainterUnits( mOffsetFromReferencePoint.y(), QgsUnitTypes::RenderMillimeters ) + context.convertToPainterUnits( mContentsMargins.top(), QgsUnitTypes::RenderMillimeters ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
painter->translate( context.convertToPainterUnits( mContentsMargins.left(), QgsUnitTypes::RenderMillimeters ),
|
||||
context.convertToPainterUnits( mContentsMargins.top(), QgsUnitTypes::RenderMillimeters ) );
|
||||
}
|
||||
QSizeF size( mFrameSize.width() - context.convertToPainterUnits( mContentsMargins.left() + mContentsMargins.right(), QgsUnitTypes::RenderMillimeters ),
|
||||
mFrameSize.height() - context.convertToPainterUnits( mContentsMargins.top() + mContentsMargins.bottom(), QgsUnitTypes::RenderMillimeters ) );
|
||||
QSizeF size( context.convertToPainterUnits( mFrameSize.width(), QgsUnitTypes::RenderMillimeters ) - context.convertToPainterUnits( mContentsMargins.left() + mContentsMargins.right(), QgsUnitTypes::RenderMillimeters ),
|
||||
context.convertToPainterUnits( mFrameSize.height(), QgsUnitTypes::RenderMillimeters ) - context.convertToPainterUnits( mContentsMargins.top() + mContentsMargins.bottom(), QgsUnitTypes::RenderMillimeters ) );
|
||||
|
||||
// scale back from painter dpi to 96 dpi --
|
||||
// double dotsPerMM = context.painter()->device()->logicalDpiX() / ( 25.4 * 3.78 );
|
||||
// context.painter()->scale( dotsPerMM, dotsPerMM );
|
||||
|
||||
renderAnnotation( context, size );
|
||||
painter->restore();
|
||||
@ -168,10 +198,10 @@ void QgsAnnotation::updateBalloon()
|
||||
|
||||
//edge list
|
||||
QList<QLineF> segmentList;
|
||||
segmentList << segment( 0 );
|
||||
segmentList << segment( 1 );
|
||||
segmentList << segment( 2 );
|
||||
segmentList << segment( 3 );
|
||||
segmentList << segment( 0, nullptr );
|
||||
segmentList << segment( 1, nullptr );
|
||||
segmentList << segment( 2, nullptr );
|
||||
segmentList << segment( 3, nullptr );
|
||||
|
||||
//find closest edge / closest edge point
|
||||
double minEdgeDist = std::numeric_limits<double>::max();
|
||||
@ -199,38 +229,56 @@ void QgsAnnotation::updateBalloon()
|
||||
return;
|
||||
}
|
||||
|
||||
//make that configurable for the item
|
||||
double segmentPointWidth = 10;
|
||||
|
||||
mBalloonSegment = minEdgeIndex;
|
||||
QPointF minEdgeEnd = minEdge.p2();
|
||||
mBalloonSegmentPoint1 = QPointF( minEdgePoint.x(), minEdgePoint.y() );
|
||||
if ( std::sqrt( minEdgePoint.sqrDist( minEdgeEnd.x(), minEdgeEnd.y() ) ) < segmentPointWidth )
|
||||
if ( std::sqrt( minEdgePoint.sqrDist( minEdgeEnd.x(), minEdgeEnd.y() ) ) < mSegmentPointWidthMm )
|
||||
{
|
||||
mBalloonSegmentPoint1 = pointOnLineWithDistance( minEdge.p2(), minEdge.p1(), segmentPointWidth );
|
||||
double x = 0;
|
||||
double y = 0;
|
||||
QgsGeometryUtils::pointOnLineWithDistance( minEdge.p2().x(), minEdge.p2().y(), minEdge.p1().x(), minEdge.p1().y(), mSegmentPointWidthMm, x, y );
|
||||
mBalloonSegmentPoint1 = QPointF( x, y );
|
||||
}
|
||||
|
||||
{
|
||||
double x = 0;
|
||||
double y = 0;
|
||||
QgsGeometryUtils::pointOnLineWithDistance( mBalloonSegmentPoint1.x(), mBalloonSegmentPoint1.y(), minEdge.p2().x(), minEdge.p2().y(), mSegmentPointWidthMm, x, y );
|
||||
mBalloonSegmentPoint2 = QPointF( x, y );
|
||||
}
|
||||
|
||||
mBalloonSegmentPoint2 = pointOnLineWithDistance( mBalloonSegmentPoint1, minEdge.p2(), 10 );
|
||||
}
|
||||
|
||||
QLineF QgsAnnotation::segment( int index ) const
|
||||
QLineF QgsAnnotation::segment( int index, QgsRenderContext *context ) const
|
||||
{
|
||||
auto scaleSize = [context]( double size )->double
|
||||
{
|
||||
return context ? context->convertToPainterUnits( size, QgsUnitTypes::RenderMillimeters ) : size;
|
||||
};
|
||||
if ( mHasFixedMapPosition )
|
||||
{
|
||||
switch ( index )
|
||||
{
|
||||
case 0:
|
||||
return QLineF( mOffsetFromReferencePoint.x(), mOffsetFromReferencePoint.y(), mOffsetFromReferencePoint.x()
|
||||
+ mFrameSize.width(), mOffsetFromReferencePoint.y() );
|
||||
return QLineF( scaleSize( mOffsetFromReferencePoint.x() ),
|
||||
scaleSize( mOffsetFromReferencePoint.y() ),
|
||||
scaleSize( mOffsetFromReferencePoint.x() ) + scaleSize( mFrameSize.width() ),
|
||||
scaleSize( mOffsetFromReferencePoint.y() ) );
|
||||
case 1:
|
||||
return QLineF( mOffsetFromReferencePoint.x() + mFrameSize.width(), mOffsetFromReferencePoint.y(),
|
||||
mOffsetFromReferencePoint.x() + mFrameSize.width(), mOffsetFromReferencePoint.y() + mFrameSize.height() );
|
||||
return QLineF( scaleSize( mOffsetFromReferencePoint.x() ) + scaleSize( mFrameSize.width() ),
|
||||
scaleSize( mOffsetFromReferencePoint.y() ),
|
||||
scaleSize( mOffsetFromReferencePoint.x() ) + scaleSize( mFrameSize.width() ),
|
||||
scaleSize( mOffsetFromReferencePoint.y() ) + scaleSize( mFrameSize.height() ) );
|
||||
case 2:
|
||||
return QLineF( mOffsetFromReferencePoint.x() + mFrameSize.width(), mOffsetFromReferencePoint.y() + mFrameSize.height(),
|
||||
mOffsetFromReferencePoint.x(), mOffsetFromReferencePoint.y() + mFrameSize.height() );
|
||||
return QLineF( scaleSize( mOffsetFromReferencePoint.x() ) + scaleSize( mFrameSize.width() ),
|
||||
scaleSize( mOffsetFromReferencePoint.y() ) + scaleSize( mFrameSize.height() ),
|
||||
scaleSize( mOffsetFromReferencePoint.x() ),
|
||||
scaleSize( mOffsetFromReferencePoint.y() ) + scaleSize( mFrameSize.height() ) );
|
||||
case 3:
|
||||
return QLineF( mOffsetFromReferencePoint.x(), mOffsetFromReferencePoint.y() + mFrameSize.height(),
|
||||
mOffsetFromReferencePoint.x(), mOffsetFromReferencePoint.y() );
|
||||
return QLineF( scaleSize( mOffsetFromReferencePoint.x() ),
|
||||
scaleSize( mOffsetFromReferencePoint.y() ) + scaleSize( mFrameSize.height() ),
|
||||
scaleSize( mOffsetFromReferencePoint.x() ),
|
||||
scaleSize( mOffsetFromReferencePoint.y() ) );
|
||||
default:
|
||||
return QLineF();
|
||||
}
|
||||
@ -240,15 +288,15 @@ QLineF QgsAnnotation::segment( int index ) const
|
||||
switch ( index )
|
||||
{
|
||||
case 0:
|
||||
return QLineF( 0, 0, mFrameSize.width(), 0 );
|
||||
return QLineF( 0, 0, scaleSize( mFrameSize.width() ), 0 );
|
||||
case 1:
|
||||
return QLineF( mFrameSize.width(), 0,
|
||||
mFrameSize.width(), mFrameSize.height() );
|
||||
return QLineF( scaleSize( mFrameSize.width() ), 0,
|
||||
scaleSize( mFrameSize.width() ), scaleSize( mFrameSize.height() ) );
|
||||
case 2:
|
||||
return QLineF( mFrameSize.width(), mFrameSize.height(),
|
||||
0, mFrameSize.height() );
|
||||
return QLineF( scaleSize( mFrameSize.width() ), scaleSize( mFrameSize.height() ),
|
||||
0, scaleSize( mFrameSize.height() ) );
|
||||
case 3:
|
||||
return QLineF( 0, mFrameSize.height(),
|
||||
return QLineF( 0, scaleSize( mFrameSize.height() ),
|
||||
0, 0 );
|
||||
default:
|
||||
return QLineF();
|
||||
@ -256,16 +304,6 @@ QLineF QgsAnnotation::segment( int index ) const
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QPointF QgsAnnotation::pointOnLineWithDistance( QPointF startPoint, QPointF directionPoint, double distance ) const
|
||||
{
|
||||
double dx = directionPoint.x() - startPoint.x();
|
||||
double dy = directionPoint.y() - startPoint.y();
|
||||
double length = std::sqrt( dx * dx + dy * dy );
|
||||
double scaleFactor = distance / length;
|
||||
return QPointF( startPoint.x() + dx * scaleFactor, startPoint.y() + dy * scaleFactor );
|
||||
}
|
||||
|
||||
void QgsAnnotation::drawFrame( QgsRenderContext &context ) const
|
||||
{
|
||||
if ( !mFillSymbol )
|
||||
@ -274,18 +312,22 @@ void QgsAnnotation::drawFrame( QgsRenderContext &context ) const
|
||||
context.painter()->setRenderHint( QPainter::Antialiasing, context.flags() & QgsRenderContext::Antialiasing );
|
||||
|
||||
QPolygonF poly;
|
||||
poly.reserve( 9 + ( mHasFixedMapPosition ? 3 : 0 ) );
|
||||
QList<QPolygonF> rings; //empty list
|
||||
for ( int i = 0; i < 4; ++i )
|
||||
{
|
||||
QLineF currentSegment = segment( i );
|
||||
poly << currentSegment.p1();
|
||||
QLineF currentSegment = segment( i, &context );
|
||||
poly << QPointF( currentSegment.p1().x(),
|
||||
currentSegment.p1().y() );
|
||||
if ( i == mBalloonSegment && mHasFixedMapPosition )
|
||||
{
|
||||
poly << mBalloonSegmentPoint1;
|
||||
poly << QPointF( context.convertToPainterUnits( mBalloonSegmentPoint1.x(), QgsUnitTypes::RenderMillimeters ),
|
||||
context.convertToPainterUnits( mBalloonSegmentPoint1.y(), QgsUnitTypes::RenderMillimeters ) );
|
||||
poly << QPointF( 0, 0 );
|
||||
poly << mBalloonSegmentPoint2;
|
||||
poly << QPointF( context.convertToPainterUnits( mBalloonSegmentPoint2.x(), QgsUnitTypes::RenderMillimeters ),
|
||||
context.convertToPainterUnits( mBalloonSegmentPoint2.y(), QgsUnitTypes::RenderMillimeters ) );
|
||||
}
|
||||
poly << currentSegment.p2();
|
||||
poly << QPointF( currentSegment.p2().x(), currentSegment.p2().y() );
|
||||
}
|
||||
if ( poly.at( 0 ) != poly.at( poly.count() - 1 ) )
|
||||
poly << poly.at( 0 );
|
||||
@ -322,10 +364,10 @@ void QgsAnnotation::_writeXml( QDomElement &itemElem, QDomDocument &doc, const Q
|
||||
annotationElem.setAttribute( QStringLiteral( "mapPosY" ), qgsDoubleToString( mMapPosition.y() ) );
|
||||
if ( mMapPositionCrs.isValid() )
|
||||
mMapPositionCrs.writeXml( annotationElem, doc );
|
||||
annotationElem.setAttribute( QStringLiteral( "offsetX" ), qgsDoubleToString( mOffsetFromReferencePoint.x() ) );
|
||||
annotationElem.setAttribute( QStringLiteral( "offsetY" ), qgsDoubleToString( mOffsetFromReferencePoint.y() ) );
|
||||
annotationElem.setAttribute( QStringLiteral( "frameWidth" ), qgsDoubleToString( mFrameSize.width() ) );
|
||||
annotationElem.setAttribute( QStringLiteral( "frameHeight" ), qgsDoubleToString( mFrameSize.height() ) );
|
||||
annotationElem.setAttribute( QStringLiteral( "offsetXMM" ), qgsDoubleToString( mOffsetFromReferencePoint.x() ) );
|
||||
annotationElem.setAttribute( QStringLiteral( "offsetYMM" ), qgsDoubleToString( mOffsetFromReferencePoint.y() ) );
|
||||
annotationElem.setAttribute( QStringLiteral( "frameWidthMM" ), qgsDoubleToString( mFrameSize.width() ) );
|
||||
annotationElem.setAttribute( QStringLiteral( "frameHeightMM" ), qgsDoubleToString( mFrameSize.height() ) );
|
||||
annotationElem.setAttribute( QStringLiteral( "canvasPosX" ), qgsDoubleToString( mRelativePosition.x() ) );
|
||||
annotationElem.setAttribute( QStringLiteral( "canvasPosY" ), qgsDoubleToString( mRelativePosition.y() ) );
|
||||
annotationElem.setAttribute( QStringLiteral( "contentsMargin" ), mContentsMargins.toString() );
|
||||
@ -379,10 +421,25 @@ void QgsAnnotation::_readXml( const QDomElement &annotationElem, const QgsReadWr
|
||||
}
|
||||
|
||||
mContentsMargins = QgsMargins::fromString( annotationElem.attribute( QStringLiteral( "contentsMargin" ) ) );
|
||||
mFrameSize.setWidth( annotationElem.attribute( QStringLiteral( "frameWidth" ), QStringLiteral( "50" ) ).toDouble() );
|
||||
mFrameSize.setHeight( annotationElem.attribute( QStringLiteral( "frameHeight" ), QStringLiteral( "50" ) ).toDouble() );
|
||||
mOffsetFromReferencePoint.setX( annotationElem.attribute( QStringLiteral( "offsetX" ), QStringLiteral( "0" ) ).toDouble() );
|
||||
mOffsetFromReferencePoint.setY( annotationElem.attribute( QStringLiteral( "offsetY" ), QStringLiteral( "0" ) ).toDouble() );
|
||||
const double dpiScale = 25.4 / qt_defaultDpiX();
|
||||
if ( annotationElem.hasAttribute( QStringLiteral( "frameWidthMM" ) ) )
|
||||
mFrameSize.setWidth( annotationElem.attribute( QStringLiteral( "frameWidthMM" ), QStringLiteral( "5" ) ).toDouble() );
|
||||
else
|
||||
mFrameSize.setWidth( dpiScale * annotationElem.attribute( QStringLiteral( "frameWidth" ), QStringLiteral( "50" ) ).toDouble() );
|
||||
if ( annotationElem.hasAttribute( QStringLiteral( "frameHeightMM" ) ) )
|
||||
mFrameSize.setHeight( annotationElem.attribute( QStringLiteral( "frameHeightMM" ), QStringLiteral( "3" ) ).toDouble() );
|
||||
else
|
||||
mFrameSize.setHeight( dpiScale * annotationElem.attribute( QStringLiteral( "frameHeight" ), QStringLiteral( "50" ) ).toDouble() );
|
||||
|
||||
if ( annotationElem.hasAttribute( QStringLiteral( "offsetXMM" ) ) )
|
||||
mOffsetFromReferencePoint.setX( annotationElem.attribute( QStringLiteral( "offsetXMM" ), QStringLiteral( "0" ) ).toDouble() );
|
||||
else
|
||||
mOffsetFromReferencePoint.setX( dpiScale * annotationElem.attribute( QStringLiteral( "offsetX" ), QStringLiteral( "0" ) ).toDouble() );
|
||||
if ( annotationElem.hasAttribute( QStringLiteral( "offsetYMM" ) ) )
|
||||
mOffsetFromReferencePoint.setY( annotationElem.attribute( QStringLiteral( "offsetYMM" ), QStringLiteral( "0" ) ).toDouble() );
|
||||
else
|
||||
mOffsetFromReferencePoint.setY( dpiScale * annotationElem.attribute( QStringLiteral( "offsetY" ), QStringLiteral( "0" ) ).toDouble() );
|
||||
|
||||
mHasFixedMapPosition = annotationElem.attribute( QStringLiteral( "mapPositionFixed" ), QStringLiteral( "1" ) ).toInt();
|
||||
mVisible = annotationElem.attribute( QStringLiteral( "visible" ), QStringLiteral( "1" ) ).toInt();
|
||||
if ( annotationElem.hasAttribute( QStringLiteral( "mapLayer" ) ) )
|
||||
@ -455,6 +512,7 @@ void QgsAnnotation::copyCommonProperties( QgsAnnotation *target ) const
|
||||
target->mBalloonSegment = mBalloonSegment;
|
||||
target->mBalloonSegmentPoint1 = mBalloonSegmentPoint1;
|
||||
target->mBalloonSegmentPoint2 = mBalloonSegmentPoint2;
|
||||
target->mSegmentPointWidthMm = mSegmentPointWidthMm;
|
||||
target->mMapLayer = mMapLayer;
|
||||
target->mFeature = mFeature;
|
||||
}
|
||||
|
@ -157,30 +157,64 @@ class CORE_EXPORT QgsAnnotation : public QObject
|
||||
void setRelativePosition( QPointF position );
|
||||
|
||||
/**
|
||||
* Sets the annotation's frame's offset from the mapPosition() reference point.
|
||||
* Sets the annotation's frame's offset (in pixels) from the mapPosition() reference point.
|
||||
* \see frameOffsetFromReferencePoint()
|
||||
* \deprecated use setFrameOffsetFromReferencePointMm() instead
|
||||
*/
|
||||
void setFrameOffsetFromReferencePoint( QPointF offset );
|
||||
Q_DECL_DEPRECATED void setFrameOffsetFromReferencePoint( QPointF offset ) SIP_DEPRECATED;
|
||||
|
||||
/**
|
||||
* Returns the annotation's frame's offset from the mapPosition() reference point.
|
||||
* Returns the annotation's frame's offset (in pixels) from the mapPosition() reference point.
|
||||
* \see setFrameOffsetFromReferencePoint()
|
||||
* \deprecated use frameOffsetFromReferencePointMm() instead
|
||||
*/
|
||||
QPointF frameOffsetFromReferencePoint() const { return mOffsetFromReferencePoint; }
|
||||
Q_DECL_DEPRECATED QPointF frameOffsetFromReferencePoint() const SIP_DEPRECATED;
|
||||
|
||||
/**
|
||||
* Sets the size of the annotation's frame (the main area in which
|
||||
* Sets the annotation's frame's offset (in millimeters) from the mapPosition() reference point.
|
||||
* \see frameOffsetFromReferencePointMm()
|
||||
* \since QGIS 3.4.8
|
||||
*/
|
||||
void setFrameOffsetFromReferencePointMm( QPointF offset );
|
||||
|
||||
/**
|
||||
* Returns the annotation's frame's offset (in millimeters) from the mapPosition() reference point.
|
||||
* \see setFrameOffsetFromReferencePointMm()
|
||||
* \since QGIS 3.4.8
|
||||
*/
|
||||
QPointF frameOffsetFromReferencePointMm() const { return mOffsetFromReferencePoint; }
|
||||
|
||||
/**
|
||||
* Sets the size (in pixels) of the annotation's frame (the main area in which
|
||||
* the annotation's content is drawn).
|
||||
* \see frameSize()
|
||||
* \deprecated use setFrameSizeMm() instead
|
||||
*/
|
||||
void setFrameSize( QSizeF size );
|
||||
Q_DECL_DEPRECATED void setFrameSize( QSizeF size ) SIP_DEPRECATED;
|
||||
|
||||
/**
|
||||
* Returns the size of the annotation's frame (the main area in which
|
||||
* Returns the size (in pixels) of the annotation's frame (the main area in which
|
||||
* the annotation's content is drawn).
|
||||
* \see setFrameSize()
|
||||
* \deprecated use frameSizeMm() instead
|
||||
*/
|
||||
QSizeF frameSize() const { return mFrameSize; }
|
||||
Q_DECL_DEPRECATED QSizeF frameSize() const SIP_DEPRECATED;
|
||||
|
||||
/**
|
||||
* Sets the size (in millimeters) of the annotation's frame (the main area in which
|
||||
* the annotation's content is drawn).
|
||||
* \see frameSizeMm()
|
||||
* \since QGIS 3.4.8
|
||||
*/
|
||||
void setFrameSizeMm( QSizeF size );
|
||||
|
||||
/**
|
||||
* Returns the size (in millimeters) of the annotation's frame (the main area in which
|
||||
* the annotation's content is drawn).
|
||||
* \see setFrameSizeMm()
|
||||
* \since QGIS 3.4.8
|
||||
*/
|
||||
QSizeF frameSizeMm() const { return mFrameSize; }
|
||||
|
||||
/**
|
||||
* Sets the margins (in millimeters) between the outside of the frame and the annotation
|
||||
@ -332,10 +366,7 @@ class CORE_EXPORT QgsAnnotation : public QObject
|
||||
void updateBalloon();
|
||||
|
||||
//! Gets the frame line (0 is the top line, 1 right, 2 bottom, 3 left)
|
||||
QLineF segment( int index ) const;
|
||||
|
||||
//! Returns a point on the line from startPoint to directionPoint that is a certain distance away from the starting point
|
||||
QPointF pointOnLineWithDistance( QPointF startPoint, QPointF directionPoint, double distance ) const;
|
||||
QLineF segment( int index, QgsRenderContext *context ) const;
|
||||
|
||||
//! Draws the annotation frame to a destination painter
|
||||
void drawFrame( QgsRenderContext &context ) const;
|
||||
@ -357,10 +388,10 @@ class CORE_EXPORT QgsAnnotation : public QObject
|
||||
//! Relative position (for non-fixed items) (0-1)
|
||||
QPointF mRelativePosition;
|
||||
|
||||
//! Describes the shift of the item content box to the reference point
|
||||
QPointF mOffsetFromReferencePoint = QPointF( 50, -50 );
|
||||
//! Describes the shift of the item content box to the reference point in mm
|
||||
QPointF mOffsetFromReferencePoint = QPointF( 13, -13 );
|
||||
|
||||
//! Size of the frame (without balloon)
|
||||
//! Size of the frame in mm (without balloon)
|
||||
QSizeF mFrameSize;
|
||||
|
||||
//! Point symbol that is to be drawn at the map reference location
|
||||
@ -374,10 +405,10 @@ class CORE_EXPORT QgsAnnotation : public QObject
|
||||
//! Segment number where the connection to the map point is attached. -1 if no balloon needed (e.g. if point is contained in frame)
|
||||
int mBalloonSegment = -1;
|
||||
|
||||
//! First segment point for drawing the connection (ccw direction)
|
||||
//! First segment point for drawing the connection (ccw direction) (always in mm)
|
||||
QPointF mBalloonSegmentPoint1;
|
||||
|
||||
//! Second segment point for drawing the balloon connection (ccw direction)
|
||||
//! Second segment point for drawing the balloon connection (ccw direction) (always in mm)
|
||||
QPointF mBalloonSegmentPoint2;
|
||||
|
||||
//! Associated layer (or NULLPTR if not attached to a layer)
|
||||
@ -386,6 +417,9 @@ class CORE_EXPORT QgsAnnotation : public QObject
|
||||
//! Associated feature, or invalid feature if no feature associated
|
||||
QgsFeature mFeature;
|
||||
|
||||
//! Roughly 10 pixels at 96 dpi, to match earlier version rendering
|
||||
double mSegmentPointWidthMm = 2.64;
|
||||
|
||||
};
|
||||
|
||||
#endif // QGSANNOTATION_H
|
||||
|
@ -83,8 +83,16 @@ void QgsHtmlAnnotation::renderAnnotation( QgsRenderContext &context, QSizeF size
|
||||
return;
|
||||
}
|
||||
|
||||
// scale painter back to 96 dpi, so layout prints match screen rendering
|
||||
context.painter()->save();
|
||||
const double scaleFactor = context.painter()->device()->logicalDpiX() / 96.0;
|
||||
context.painter()->scale( scaleFactor, scaleFactor );
|
||||
size /= scaleFactor;
|
||||
|
||||
mWebPage->setViewportSize( size.toSize() );
|
||||
mWebPage->mainFrame()->render( context.painter() );
|
||||
|
||||
context.painter()->restore();
|
||||
}
|
||||
|
||||
QSizeF QgsHtmlAnnotation::minimumFrameSize() const
|
||||
|
@ -1,10 +1,10 @@
|
||||
/***************************************************************************
|
||||
qgstextannotation.cpp
|
||||
------------------------
|
||||
begin : February 9, 2010
|
||||
copyright : (C) 2010 by Marco Hugentobler
|
||||
email : marco dot hugentobler at hugis dot net
|
||||
***************************************************************************/
|
||||
begin : February 9, 2010
|
||||
copyright : (C) 2010 by Marco Hugentobler
|
||||
email : marco dot hugentobler at hugis dot net
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* *
|
||||
@ -56,6 +56,12 @@ void QgsTextAnnotation::renderAnnotation( QgsRenderContext &context, QSizeF size
|
||||
return;
|
||||
}
|
||||
|
||||
// scale painter back to 96 dpi, so layout prints match screen rendering
|
||||
context.painter()->save();
|
||||
const double scaleFactor = context.painter()->device()->logicalDpiX() / 96.0;
|
||||
context.painter()->scale( scaleFactor, scaleFactor );
|
||||
size /= scaleFactor;
|
||||
|
||||
mDocument->setTextWidth( size.width() );
|
||||
|
||||
QRectF clipRect = QRectF( 0, 0, size.width(), size.height() );
|
||||
@ -68,6 +74,8 @@ void QgsTextAnnotation::renderAnnotation( QgsRenderContext &context, QSizeF size
|
||||
}
|
||||
//draw text document
|
||||
mDocument->drawContents( painter, clipRect );
|
||||
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
void QgsTextAnnotation::writeXml( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const
|
||||
|
@ -62,7 +62,8 @@ void QgsFormAnnotation::setDesignerForm( const QString &uiFile )
|
||||
newFill->setColor( mDesignerWidget->palette().color( QPalette::Window ) );
|
||||
setFillSymbol( newFill );
|
||||
}
|
||||
setFrameSize( preferredFrameSize() );
|
||||
// convert from size in pixels at 96 dpi to mm
|
||||
setFrameSizeMm( preferredFrameSize() / 3.7795275 );
|
||||
}
|
||||
emit appearanceChanged();
|
||||
}
|
||||
@ -112,10 +113,18 @@ void QgsFormAnnotation::renderAnnotation( QgsRenderContext &context, QSizeF size
|
||||
if ( !mDesignerWidget )
|
||||
return;
|
||||
|
||||
// scale painter back to 96 dpi, so that forms look good even in layout prints
|
||||
context.painter()->save();
|
||||
const double scaleFactor = context.painter()->device()->logicalDpiX() / 96.0;
|
||||
context.painter()->scale( scaleFactor, scaleFactor );
|
||||
size /= scaleFactor;
|
||||
|
||||
mDesignerWidget->setFixedSize( size.toSize() );
|
||||
context.painter()->setBrush( Qt::NoBrush );
|
||||
context.painter()->setPen( Qt::NoPen );
|
||||
mDesignerWidget->render( context.painter(), QPoint( 0, 0 ) );
|
||||
|
||||
context.painter()->restore();
|
||||
}
|
||||
|
||||
QSizeF QgsFormAnnotation::minimumFrameSize() const
|
||||
|
@ -94,9 +94,13 @@ void QgsMapCanvasAnnotationItem::updateBoundingRect()
|
||||
double fillSymbolBleed = mAnnotation && mAnnotation->fillSymbol() ?
|
||||
QgsSymbolLayerUtils::estimateMaxSymbolBleed( mAnnotation->fillSymbol(), rc ) : 0;
|
||||
|
||||
const double mmToPixelScale = mMapCanvas->logicalDpiX() / 25.4;
|
||||
|
||||
if ( mAnnotation && !mAnnotation->hasFixedMapPosition() )
|
||||
{
|
||||
mBoundingRect = QRectF( - fillSymbolBleed, -fillSymbolBleed, mAnnotation->frameSize().width() + fillSymbolBleed * 2, mAnnotation->frameSize().height() + fillSymbolBleed * 2 );
|
||||
mBoundingRect = QRectF( - fillSymbolBleed, -fillSymbolBleed,
|
||||
mmToPixelScale * mAnnotation->frameSizeMm().width() + fillSymbolBleed * 2,
|
||||
mmToPixelScale * mAnnotation->frameSizeMm().height() + fillSymbolBleed * 2 );
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -106,9 +110,11 @@ void QgsMapCanvasAnnotationItem::updateBoundingRect()
|
||||
halfSymbolSize = scaledSymbolSize() / 2.0;
|
||||
}
|
||||
|
||||
QPointF offset = mAnnotation ? mAnnotation->frameOffsetFromReferencePoint() : QPointF( 0, 0 );
|
||||
QPointF offset = mAnnotation ? QPointF( mAnnotation->frameOffsetFromReferencePointMm().x() * mmToPixelScale,
|
||||
mAnnotation->frameOffsetFromReferencePointMm().y() * mmToPixelScale ) : QPointF( 0, 0 );
|
||||
|
||||
QSizeF frameSize = mAnnotation ? mAnnotation->frameSize() : QSizeF( 0.0, 0.0 );
|
||||
QSizeF frameSize = mAnnotation ? QSizeF( mAnnotation->frameSizeMm().width() * mmToPixelScale,
|
||||
mAnnotation->frameSizeMm().height() * mmToPixelScale ) : QSizeF( 0.0, 0.0 );
|
||||
|
||||
double xMinPos = std::min( -halfSymbolSize, offset.x() - fillSymbolBleed );
|
||||
double xMaxPos = std::max( halfSymbolSize, offset.x() + frameSize.width() + fillSymbolBleed );
|
||||
@ -196,8 +202,10 @@ QgsMapCanvasAnnotationItem::MouseMoveAction QgsMapCanvasAnnotationItem::moveActi
|
||||
return MoveMapPosition;
|
||||
}
|
||||
|
||||
QPointF offset = mAnnotation && mAnnotation->hasFixedMapPosition() ? mAnnotation->frameOffsetFromReferencePoint() : QPointF( 0, 0 );
|
||||
QSizeF frameSize = mAnnotation ? mAnnotation->frameSize() : QSizeF( 0, 0 );
|
||||
const double mmToPixelScale = mMapCanvas->logicalDpiX() / 25.4;
|
||||
|
||||
QPointF offset = mAnnotation && mAnnotation->hasFixedMapPosition() ? mAnnotation->frameOffsetFromReferencePointMm() * mmToPixelScale : QPointF( 0, 0 );
|
||||
QSizeF frameSize = mAnnotation ? mAnnotation->frameSizeMm() * mmToPixelScale : QSizeF( 0, 0 );
|
||||
|
||||
bool left, right, up, down;
|
||||
left = std::fabs( itemPos.x() - offset.x() ) < cursorSensitivity;
|
||||
|
@ -26,14 +26,22 @@ from qgis.core import (QgsTextAnnotation,
|
||||
QgsVectorLayer,
|
||||
QgsFeature,
|
||||
QgsMargins,
|
||||
QgsFillSymbol)
|
||||
QgsFillSymbol,
|
||||
QgsProject,
|
||||
QgsLayout,
|
||||
QgsLayoutItemMap,
|
||||
QgsPointXY)
|
||||
from qgis.gui import QgsFormAnnotation
|
||||
from qgis.PyQt.QtCore import (QDir,
|
||||
QPointF,
|
||||
QSizeF)
|
||||
QSize,
|
||||
QSizeF,
|
||||
QRectF)
|
||||
from qgis.PyQt.QtGui import (QColor,
|
||||
QPainter,
|
||||
QImage,
|
||||
QTextDocument)
|
||||
from qgslayoutchecker import QgsLayoutChecker
|
||||
|
||||
from qgis.testing import start_app, unittest
|
||||
from utilities import unitTestDataPath
|
||||
@ -57,8 +65,8 @@ class TestQgsAnnotation(unittest.TestCase):
|
||||
a = QgsTextAnnotation()
|
||||
a.fillSymbol().symbolLayer(0).setStrokeColor(QColor(0, 0, 0))
|
||||
a.markerSymbol().symbolLayer(0).setStrokeColor(QColor(0, 0, 0))
|
||||
a.setFrameSize(QSizeF(300, 200))
|
||||
a.setFrameOffsetFromReferencePoint(QPointF(40, 50))
|
||||
a.setFrameSizeMm(QSizeF(300 / 3.7795275, 200 / 3.7795275))
|
||||
a.setFrameOffsetFromReferencePointMm(QPointF(40 / 3.7795275, 50 / 3.7795275))
|
||||
doc = QTextDocument()
|
||||
doc.setHtml('<p style="font-family: arial; font-weight: bold; font-size: 40px;">test annotation</p>')
|
||||
a.setDocument(doc)
|
||||
@ -67,16 +75,28 @@ class TestQgsAnnotation(unittest.TestCase):
|
||||
|
||||
# check clone
|
||||
clone = a.clone()
|
||||
im = self.renderAnnotation(a, QPointF(20, 30))
|
||||
im = self.renderAnnotation(clone, QPointF(20, 30))
|
||||
self.assertTrue(self.imageCheck('text_annotation', 'text_annotation', im))
|
||||
|
||||
def testTextAnnotationInLayout(self):
|
||||
""" test rendering a text annotation"""
|
||||
a = QgsTextAnnotation()
|
||||
a.fillSymbol().symbolLayer(0).setStrokeColor(QColor(0, 0, 0))
|
||||
a.markerSymbol().symbolLayer(0).setStrokeColor(QColor(0, 0, 0))
|
||||
a.setFrameSizeMm(QSizeF(300 / 3.7795275, 200 / 3.7795275))
|
||||
a.setFrameOffsetFromReferencePointMm(QPointF(40 / 3.7795275, 50 / 3.7795275))
|
||||
doc = QTextDocument()
|
||||
doc.setHtml('<p style="font-family: arial; font-weight: bold; font-size: 40px;">test annotation</p>')
|
||||
a.setDocument(doc)
|
||||
self.assertTrue(self.renderAnnotationInLayout('text_annotation_in_layout', a))
|
||||
|
||||
def testSvgAnnotation(self):
|
||||
""" test rendering a svg annotation"""
|
||||
a = QgsSvgAnnotation()
|
||||
a.fillSymbol().symbolLayer(0).setStrokeColor(QColor(0, 0, 0))
|
||||
a.markerSymbol().symbolLayer(0).setStrokeColor(QColor(0, 0, 0))
|
||||
a.setFrameSize(QSizeF(300, 200))
|
||||
a.setFrameOffsetFromReferencePoint(QPointF(40, 50))
|
||||
a.setFrameSizeMm(QSizeF(300 / 3.7795275, 200 / 3.7795275))
|
||||
a.setFrameOffsetFromReferencePointMm(QPointF(40 / 3.7795275, 50 / 3.7795275))
|
||||
svg = TEST_DATA_DIR + "/sample_svg.svg"
|
||||
a.setFilePath(svg)
|
||||
im = self.renderAnnotation(a, QPointF(20, 30))
|
||||
@ -84,16 +104,27 @@ class TestQgsAnnotation(unittest.TestCase):
|
||||
|
||||
# check clone
|
||||
clone = a.clone()
|
||||
im = self.renderAnnotation(a, QPointF(20, 30))
|
||||
im = self.renderAnnotation(clone, QPointF(20, 30))
|
||||
self.assertTrue(self.imageCheck('svg_annotation', 'svg_annotation', im))
|
||||
|
||||
def testSvgAnnotationInLayout(self):
|
||||
""" test rendering a svg annotation"""
|
||||
a = QgsSvgAnnotation()
|
||||
a.fillSymbol().symbolLayer(0).setStrokeColor(QColor(0, 0, 0))
|
||||
a.markerSymbol().symbolLayer(0).setStrokeColor(QColor(0, 0, 0))
|
||||
a.setFrameSizeMm(QSizeF(300 / 3.7795275, 200 / 3.7795275))
|
||||
a.setFrameOffsetFromReferencePointMm(QPointF(40 / 3.7795275, 50 / 3.7795275))
|
||||
svg = TEST_DATA_DIR + "/sample_svg.svg"
|
||||
a.setFilePath(svg)
|
||||
self.assertTrue(self.renderAnnotationInLayout('svg_annotation_in_layout', a))
|
||||
|
||||
def testHtmlAnnotation(self):
|
||||
""" test rendering a html annotation"""
|
||||
a = QgsHtmlAnnotation()
|
||||
a.fillSymbol().symbolLayer(0).setStrokeColor(QColor(0, 0, 0))
|
||||
a.markerSymbol().symbolLayer(0).setStrokeColor(QColor(0, 0, 0))
|
||||
a.setFrameSize(QSizeF(400, 250))
|
||||
a.setFrameOffsetFromReferencePoint(QPointF(70, 90))
|
||||
a.setFrameSizeMm(QSizeF(400 / 3.7795275, 250 / 3.7795275))
|
||||
a.setFrameOffsetFromReferencePointMm(QPointF(70 / 3.7795275, 90 / 3.7795275))
|
||||
html = TEST_DATA_DIR + "/test_html.html"
|
||||
a.setSourceFile(html)
|
||||
im = self.renderAnnotation(a, QPointF(20, 30))
|
||||
@ -101,9 +132,20 @@ class TestQgsAnnotation(unittest.TestCase):
|
||||
|
||||
# check clone
|
||||
clone = a.clone()
|
||||
im = self.renderAnnotation(a, QPointF(20, 30))
|
||||
im = self.renderAnnotation(clone, QPointF(20, 30))
|
||||
self.assertTrue(self.imageCheck('html_annotation', 'html_annotation', im))
|
||||
|
||||
def testHtmlAnnotationInLayout(self):
|
||||
""" test rendering a svg annotation"""
|
||||
a = QgsHtmlAnnotation()
|
||||
a.fillSymbol().symbolLayer(0).setStrokeColor(QColor(0, 0, 0))
|
||||
a.markerSymbol().symbolLayer(0).setStrokeColor(QColor(0, 0, 0))
|
||||
a.setFrameSizeMm(QSizeF(400 / 3.7795275, 200 / 3.7795275))
|
||||
a.setFrameOffsetFromReferencePointMm(QPointF(70 / 3.7795275, 90 / 3.7795275))
|
||||
html = TEST_DATA_DIR + "/test_html.html"
|
||||
a.setSourceFile(html)
|
||||
self.assertTrue(self.renderAnnotationInLayout('html_annotation_in_layout', a))
|
||||
|
||||
def testHtmlAnnotationWithFeature(self):
|
||||
""" test rendering a html annotation with a feature"""
|
||||
layer = QgsVectorLayer("Point?crs=EPSG:3111&field=station:string&field=suburb:string",
|
||||
@ -112,8 +154,8 @@ class TestQgsAnnotation(unittest.TestCase):
|
||||
a = QgsHtmlAnnotation()
|
||||
a.fillSymbol().symbolLayer(0).setStrokeColor(QColor(0, 0, 0))
|
||||
a.markerSymbol().symbolLayer(0).setStrokeColor(QColor(0, 0, 0))
|
||||
a.setFrameSize(QSizeF(400, 250))
|
||||
a.setFrameOffsetFromReferencePoint(QPointF(70, 90))
|
||||
a.setFrameSizeMm(QSizeF(400 / 3.7795275, 250 / 3.7795275))
|
||||
a.setFrameOffsetFromReferencePointMm(QPointF(70 / 3.7795275, 90 / 3.7795275))
|
||||
a.setMapLayer(layer)
|
||||
html = TEST_DATA_DIR + "/test_html_feature.html"
|
||||
a.setSourceFile(html)
|
||||
@ -126,11 +168,39 @@ class TestQgsAnnotation(unittest.TestCase):
|
||||
im = self.renderAnnotation(a, QPointF(20, 30))
|
||||
self.assertTrue(self.imageCheck('html_feature', 'html_feature', im))
|
||||
|
||||
def testFormAnnotation(self):
|
||||
""" test rendering a form annotation"""
|
||||
a = QgsFormAnnotation()
|
||||
a.fillSymbol().symbolLayer(0).setStrokeColor(QColor(0, 0, 0))
|
||||
a.markerSymbol().symbolLayer(0).setStrokeColor(QColor(0, 0, 0))
|
||||
a.setFrameSizeMm(QSizeF(400 / 3.7795275, 250 / 3.7795275))
|
||||
a.setFrameOffsetFromReferencePointMm(QPointF(70 / 3.7795275, 90 / 3.7795275))
|
||||
ui = TEST_DATA_DIR + "/test_form.ui"
|
||||
a.setDesignerForm(ui)
|
||||
im = self.renderAnnotation(a, QPointF(20, 30))
|
||||
self.assertTrue(self.imageCheck('form_annotation', 'form_annotation', im))
|
||||
|
||||
# check clone
|
||||
clone = a.clone()
|
||||
im = self.renderAnnotation(clone, QPointF(20, 30))
|
||||
self.assertTrue(self.imageCheck('form_annotation', 'form_annotation', im))
|
||||
|
||||
def testFormAnnotationInLayout(self):
|
||||
""" test rendering a form annotation"""
|
||||
a = QgsFormAnnotation()
|
||||
a.fillSymbol().symbolLayer(0).setStrokeColor(QColor(0, 0, 0))
|
||||
a.markerSymbol().symbolLayer(0).setStrokeColor(QColor(0, 0, 0))
|
||||
a.setFrameSizeMm(QSizeF(400 / 3.7795275, 250 / 3.7795275))
|
||||
a.setFrameOffsetFromReferencePointMm(QPointF(70 / 3.7795275, 90 / 3.7795275))
|
||||
ui = TEST_DATA_DIR + "/test_form.ui"
|
||||
a.setDesignerForm(ui)
|
||||
self.assertTrue(self.renderAnnotationInLayout('form_annotation_in_layout', a))
|
||||
|
||||
def testRelativePosition(self):
|
||||
""" test rendering an annotation without map point"""
|
||||
a = QgsHtmlAnnotation()
|
||||
a.fillSymbol().symbolLayer(0).setStrokeColor(QColor(0, 0, 0))
|
||||
a.setFrameSize(QSizeF(400, 250))
|
||||
a.setFrameSizeMm(QSizeF(400 / 3.7795275, 250 / 3.7795275))
|
||||
a.setHasFixedMapPosition(False)
|
||||
html = TEST_DATA_DIR + "/test_html.html"
|
||||
a.setSourceFile(html)
|
||||
@ -141,7 +211,7 @@ class TestQgsAnnotation(unittest.TestCase):
|
||||
""" test rendering an annotation with margins"""
|
||||
a = QgsHtmlAnnotation()
|
||||
a.fillSymbol().symbolLayer(0).setStrokeColor(QColor(0, 0, 0))
|
||||
a.setFrameSize(QSizeF(400, 250))
|
||||
a.setFrameSizeMm(QSizeF(400 / 3.7795275, 250 / 3.7795275))
|
||||
a.setHasFixedMapPosition(False)
|
||||
a.setContentsMargin(QgsMargins(15, 10, 30, 20))
|
||||
html = TEST_DATA_DIR + "/test_html.html"
|
||||
@ -152,7 +222,7 @@ class TestQgsAnnotation(unittest.TestCase):
|
||||
def testFillSymbol(self):
|
||||
""" test rendering an annotation with fill symbol"""
|
||||
a = QgsTextAnnotation()
|
||||
a.setFrameSize(QSizeF(400, 250))
|
||||
a.setFrameSizeMm(QSizeF(400 / 3.7795275, 250 / 3.7795275))
|
||||
a.setHasFixedMapPosition(False)
|
||||
a.setFillSymbol(QgsFillSymbol.createSimple({'color': 'blue', 'width_border': '5', 'outline_color': 'black'}))
|
||||
im = self.renderAnnotation(a, QPointF(20, 30))
|
||||
@ -180,6 +250,30 @@ class TestQgsAnnotation(unittest.TestCase):
|
||||
painter.end()
|
||||
return image
|
||||
|
||||
def renderAnnotationInLayout(self, test_name, annotation):
|
||||
pr = QgsProject()
|
||||
l = QgsLayout(pr)
|
||||
l.initializeDefaults()
|
||||
map = QgsLayoutItemMap(l)
|
||||
map.attemptSetSceneRect(QRectF(20, 20, 200, 100))
|
||||
map.setFrameEnabled(True)
|
||||
rectangle = QgsRectangle(0, 0, 18, 8)
|
||||
map.setExtent(rectangle)
|
||||
l.addLayoutItem(map)
|
||||
|
||||
annotation.setMapPosition(QgsPointXY(1, 7))
|
||||
annotation.setHasFixedMapPosition(True)
|
||||
pr.annotationManager().addAnnotation(annotation)
|
||||
|
||||
checker = QgsLayoutChecker(
|
||||
test_name, l)
|
||||
checker.dots_per_meter = 2 * 96 / 25.4 * 1000
|
||||
checker.size = QSize(1122 * 2, 794 * 2)
|
||||
checker.setControlPathPrefix("annotations")
|
||||
result, message = checker.testLayout()
|
||||
self.report += checker.report()
|
||||
return result
|
||||
|
||||
def imageCheck(self, name, reference_image, image):
|
||||
self.report += "<h2>Render {}</h2>\n".format(name)
|
||||
temp_dir = QDir.tempPath() + '/'
|
||||
|
@ -38,8 +38,8 @@ class TestQgsMapCanvasAnnotationItem(unittest.TestCase):
|
||||
def testPosition(self):
|
||||
""" test that map canvas annotation item syncs position correctly """
|
||||
a = QgsTextAnnotation()
|
||||
a.setFrameSize(QSizeF(300, 200))
|
||||
a.setFrameOffsetFromReferencePoint(QPointF(40, 50))
|
||||
a.setFrameSizeMm(QSizeF(300 / 3.7795275, 200 / 3.7795275))
|
||||
a.setFrameOffsetFromReferencePointMm(QPointF(40 / 3.7795275, 50 / 3.7795275))
|
||||
a.setMapPosition(QgsPointXY(12, 34))
|
||||
a.setMapPositionCrs(QgsCoordinateReferenceSystem(4326))
|
||||
|
||||
@ -81,7 +81,7 @@ class TestQgsMapCanvasAnnotationItem(unittest.TestCase):
|
||||
def testSize(self):
|
||||
""" test that map canvas annotation item size is correct """
|
||||
a = QgsTextAnnotation()
|
||||
a.setFrameSize(QSizeF(300, 200))
|
||||
a.setFrameSizeMm(QSizeF(300 / 3.7795275, 200 / 3.7795275))
|
||||
a.setHasFixedMapPosition(False)
|
||||
a.setFillSymbol(QgsFillSymbol.createSimple({'color': 'blue', 'width_border': '0'}))
|
||||
|
||||
@ -94,6 +94,8 @@ class TestQgsMapCanvasAnnotationItem(unittest.TestCase):
|
||||
canvas.setExtent(QgsRectangle(10, 30, 20, 35))
|
||||
|
||||
i = QgsMapCanvasAnnotationItem(a, canvas)
|
||||
# ugly, but Travis has different default DPI:
|
||||
if 299 < i.boundingRect().width() < 301:
|
||||
self.assertAlmostEqual(i.boundingRect().width(), 300, 1)
|
||||
self.assertAlmostEqual(i.boundingRect().height(), 200, 1)
|
||||
|
||||
@ -104,6 +106,17 @@ class TestQgsMapCanvasAnnotationItem(unittest.TestCase):
|
||||
a.setFrameOffsetFromReferencePoint(QPointF(10, 20))
|
||||
self.assertAlmostEqual(i.boundingRect().width(), 310, -1)
|
||||
self.assertAlmostEqual(i.boundingRect().height(), 220, -1)
|
||||
else:
|
||||
self.assertAlmostEqual(i.boundingRect().width(), 312.5, 1)
|
||||
self.assertAlmostEqual(i.boundingRect().height(), 208.33, 1)
|
||||
|
||||
a.setHasFixedMapPosition(True)
|
||||
a.setFrameOffsetFromReferencePoint(QPointF(0, 0))
|
||||
self.assertAlmostEqual(i.boundingRect().width(), 312.5, -1)
|
||||
self.assertAlmostEqual(i.boundingRect().height(), 208.33, -1)
|
||||
a.setFrameOffsetFromReferencePoint(QPointF(10, 20))
|
||||
self.assertAlmostEqual(i.boundingRect().width(), 322.91, -1)
|
||||
self.assertAlmostEqual(i.boundingRect().height(), 229.166, -1)
|
||||
|
||||
def testVisibility(self):
|
||||
""" test that map canvas annotation item visibility follows layer"""
|
||||
@ -123,8 +136,8 @@ class TestQgsMapCanvasAnnotationItem(unittest.TestCase):
|
||||
def testSettingFeature(self):
|
||||
""" test that feature is set when item moves """
|
||||
a = QgsTextAnnotation()
|
||||
a.setFrameSize(QSizeF(300, 200))
|
||||
a.setFrameOffsetFromReferencePoint(QPointF(40, 50))
|
||||
a.setFrameSizeMm(QSizeF(300 / 3.7795275, 200 / 3.7795275))
|
||||
a.setFrameOffsetFromReferencePointMm(QPointF(40 / 3.7795275, 50 / 3.7795275))
|
||||
a.setHasFixedMapPosition(True)
|
||||
a.setMapPosition(QgsPointXY(12, 34))
|
||||
a.setMapPositionCrs(QgsCoordinateReferenceSystem(4326))
|
||||
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 6.6 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 23 KiB |
BIN
tests/testdata/control_images/annotations/expected_html_feature/fedora/expected_html_feature.png
vendored
Normal file
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 9.3 KiB |
Before Width: | Height: | Size: 5.6 KiB |
BIN
tests/testdata/control_images/annotations/expected_html_nofeature/fedora/expected_html_nofeature.png
vendored
Normal file
After Width: | Height: | Size: 7.1 KiB |
After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
BIN
tests/testdata/control_images/annotations/expected_relative_style/travis/expected_relative_style.png
vendored
Normal file
After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 9.1 KiB |
After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 9.7 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 13 KiB |
35
tests/testdata/test_form.ui
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton">
|
||||
<property name="text">
|
||||
<string>Test</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox">
|
||||
<property name="text">
|
||||
<string>Test</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|