Skip to content
Permalink
Browse files

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
  • Loading branch information
nyalldawson committed Apr 16, 2019
1 parent 5c79b74 commit d56b72b0e87ec162cbd416a311ca173ad9554a67
Showing with 456 additions and 143 deletions.
  1. +54 −8 python/core/auto_generated/annotations/qgsannotation.sip.in
  2. +23 −23 src/app/qgsmaptoolannotation.cpp
  3. +110 −52 src/core/annotations/qgsannotation.cpp
  4. +51 −17 src/core/annotations/qgsannotation.h
  5. +8 −0 src/core/annotations/qgshtmlannotation.cpp
  6. +14 −6 src/core/annotations/qgstextannotation.cpp
  7. +10 −1 src/gui/qgsformannotation.cpp
  8. +13 −5 src/gui/qgsmapcanvasannotationitem.cpp
  9. +110 −16 tests/src/python/test_qgsannotation.py
  10. +28 −15 tests/src/python/test_qgsmapcanvasannotationitem.py
  11. BIN ...ntrol_images/annotations/expected_annotation_margins/{ → default}/expected_annotation_margins.png
  12. BIN ..._images/annotations/expected_annotation_margins/{ → default}/expected_annotation_margins_mask.png
  13. BIN ...ata/control_images/annotations/expected_annotation_margins/travis/expected_annotation_margins.png
  14. BIN .../testdata/control_images/annotations/expected_form_annotation/fedora/expected_form_annotation.png
  15. BIN .../testdata/control_images/annotations/expected_form_annotation/travis/expected_form_annotation.png
  16. BIN ...ages/annotations/expected_form_annotation_in_layout/fedora/expected_form_annotation_in_layout.png
  17. BIN ...ages/annotations/expected_form_annotation_in_layout/travis/expected_form_annotation_in_layout.png
  18. BIN ...ata/control_images/annotations/expected_html_annotation/{ → default}/expected_html_annotation.png
  19. BIN ...ata/control_images/annotations/expected_html_annotation/default/expected_html_annotation_mask.png
  20. BIN tests/testdata/control_images/annotations/expected_html_annotation/expected_html_annotation_mask.png
  21. BIN .../testdata/control_images/annotations/expected_html_annotation/travis/expected_html_annotation.png
  22. BIN ...trol_images/annotations/expected_html_annotation_in_layout/expected_html_annotation_in_layout.png
  23. BIN tests/testdata/control_images/annotations/expected_html_feature/fedora/expected_html_feature.png
  24. BIN ...s/testdata/control_images/annotations/expected_html_feature/fedora/expected_html_feature_mask.png
  25. BIN tests/testdata/control_images/annotations/expected_html_feature/travis/expected_html_feature.png
  26. BIN ...s/testdata/control_images/annotations/expected_html_feature/travis/expected_html_feature_mask.png
  27. BIN tests/testdata/control_images/annotations/expected_html_nofeature/fedora/expected_html_nofeature.png
  28. BIN ...stdata/control_images/annotations/expected_html_nofeature/fedora/expected_html_nofeature_mask.png
  29. BIN tests/testdata/control_images/annotations/expected_html_nofeature/travis/expected_html_nofeature.png
  30. BIN ...stdata/control_images/annotations/expected_html_nofeature/travis/expected_html_nofeature_mask.png
  31. BIN ...tdata/control_images/annotations/expected_relative_style/{ → default}/expected_relative_style.png
  32. BIN .../control_images/annotations/expected_relative_style/{ → default}/expected_relative_style_mask.png
  33. BIN tests/testdata/control_images/annotations/expected_relative_style/travis/expected_relative_style.png
  34. BIN tests/testdata/control_images/annotations/expected_svg_annotation/expected_svg_annotation_mask.png
  35. BIN ...ontrol_images/annotations/expected_svg_annotation_in_layout/expected_svg_annotation_in_layout.png
  36. BIN .../testdata/control_images/annotations/expected_text_annotation/fedora/expected_text_annotation.png
  37. BIN ...data/control_images/annotations/expected_text_annotation/fedora/expected_text_annotation_mask.png
  38. BIN .../testdata/control_images/annotations/expected_text_annotation/travis/expected_text_annotation.png
  39. BIN ...ges/annotations/expected_text_annotation_in_layout/default/expected_text_annotation_in_layout.png
  40. BIN ...ages/annotations/expected_text_annotation_in_layout/travis/expected_text_annotation_in_layout.png
  41. BIN tests/testdata/control_images/qgis_server/WMS_GetMap_Annotations/WMS_GetMap_Annotations.png
  42. BIN tests/testdata/control_images/qgis_server/WMS_GetMap_Annotations/WMS_GetMap_Annotations_mask.png
  43. +35 −0 tests/testdata/test_form.ui
@@ -142,34 +142,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 setFrameOffsetFromReferencePointMm( QPointF offset );
%Docstring
Sets the annotation's frame's offset (in millimeters) from the mapPosition() reference point.

.. seealso:: :py:func:`frameOffsetFromReferencePointMm`

.. versionadded:: 3.4.8
%End

void setFrameSize( QSizeF size );
QPointF frameOffsetFromReferencePointMm() const;
%Docstring
Sets the size of the annotation's frame (the main area in which
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 );

@@ -192,7 +185,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() ) );
}
@@ -209,9 +206,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();
@@ -221,27 +221,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
@@ -259,8 +259,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 );
@@ -349,7 +349,7 @@ void QgsMapToolAnnotation::toggleTextItemVisibilities()
QList<QgsMapCanvasAnnotationItem *> itemList = annotationItems();
Q_FOREACH ( QgsMapCanvasAnnotationItem *item, itemList )
{
QgsTextAnnotation *textItem = dynamic_cast<QgsTextAnnotation *>( item->annotation() );
QgsTextAnnotation *textItem = qobject_cast<QgsTextAnnotation *>( item->annotation() );
if ( textItem )
{
textItem->setVisible( !textItem->isVisible() );

0 comments on commit d56b72b

Please sign in to comment.
You can’t perform that action at this time.