From e8a08489859a29c491766959fe9a022560202954 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 16 Aug 2024 11:52:16 +1000 Subject: [PATCH 01/15] Add framework to upgrade older annotations to annotation layer items on project load --- .../annotations/qgsannotationmanager.sip.in | 1 + .../annotations/qgsannotationmanager.sip.in | 1 + src/core/annotations/qgsannotationmanager.cpp | 61 ++++++++++++++++--- src/core/annotations/qgsannotationmanager.h | 22 ++++++- src/core/project/qgsproject.cpp | 2 +- 5 files changed, 76 insertions(+), 11 deletions(-) diff --git a/python/PyQt6/core/auto_generated/annotations/qgsannotationmanager.sip.in b/python/PyQt6/core/auto_generated/annotations/qgsannotationmanager.sip.in index 63e60224890a..70ba9b4fddd9 100644 --- a/python/PyQt6/core/auto_generated/annotations/qgsannotationmanager.sip.in +++ b/python/PyQt6/core/auto_generated/annotations/qgsannotationmanager.sip.in @@ -92,6 +92,7 @@ present in the XML document. .. seealso:: :py:func:`writeXml` %End + QDomElement writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const; %Docstring Returns a DOM element representing the state of the manager. diff --git a/python/core/auto_generated/annotations/qgsannotationmanager.sip.in b/python/core/auto_generated/annotations/qgsannotationmanager.sip.in index 63e60224890a..70ba9b4fddd9 100644 --- a/python/core/auto_generated/annotations/qgsannotationmanager.sip.in +++ b/python/core/auto_generated/annotations/qgsannotationmanager.sip.in @@ -92,6 +92,7 @@ present in the XML document. .. seealso:: :py:func:`writeXml` %End + QDomElement writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const; %Docstring Returns a DOM element representing the state of the manager. diff --git a/src/core/annotations/qgsannotationmanager.cpp b/src/core/annotations/qgsannotationmanager.cpp index 465b0c42ee12..0ef1f23872b5 100644 --- a/src/core/annotations/qgsannotationmanager.cpp +++ b/src/core/annotations/qgsannotationmanager.cpp @@ -19,6 +19,8 @@ #include "qgsannotationregistry.h" #include "qgsapplication.h" #include "qgsstyleentityvisitor.h" +#include "qgsannotationitem.h" +#include "qgsannotationlayer.h" QgsAnnotationManager::QgsAnnotationManager( QgsProject *project ) : QObject( project ) @@ -86,17 +88,52 @@ QList QgsAnnotationManager::cloneAnnotations() const } bool QgsAnnotationManager::readXml( const QDomElement &element, const QgsReadWriteContext &context ) +{ + return readXmlPrivate( element, context, nullptr ); +} + +bool QgsAnnotationManager::readXmlAndUpgradeToAnnotationLayerItems( const QDomElement &element, const QgsReadWriteContext &context, QgsAnnotationLayer *layer ) +{ + return readXmlPrivate( element, context, layer ); +} + +bool QgsAnnotationManager::readXmlPrivate( const QDomElement &element, const QgsReadWriteContext &context, QgsAnnotationLayer *layer ) { clear(); //restore each annotation bool result = true; + auto createAnnotationFromElement = [this, &context, layer]( const QDomElement & element ) + { + std::unique_ptr< QgsAnnotation > annotation( createAnnotationFromXml( element, context ) ); + if ( !annotation ) + return; + + if ( layer ) + { + std::unique_ptr< QgsAnnotationItem > annotationItem = convertToAnnotationItem( annotation.get() ); + if ( annotationItem ) + { + layer->addItem( annotationItem.release() ); + } + else + { + // could not convert to QgsAnnotationItem, just leave as QgsAnnotation + addAnnotation( annotation.release() ); + } + } + else + { + addAnnotation( annotation.release() ); + } + }; + QDomElement annotationsElem = element.firstChildElement( QStringLiteral( "Annotations" ) ); QDomElement annotationElement = annotationsElem.firstChildElement( QStringLiteral( "Annotation" ) ); - while ( ! annotationElement .isNull() ) + while ( ! annotationElement.isNull() ) { - createAnnotationFromXml( annotationElement, context ); + createAnnotationFromElement( annotationElement ); annotationElement = annotationElement.nextSiblingElement( QStringLiteral( "Annotation" ) ); } @@ -106,28 +143,34 @@ bool QgsAnnotationManager::readXml( const QDomElement &element, const QgsReadWri QDomNodeList oldItemList = element.elementsByTagName( QStringLiteral( "TextAnnotationItem" ) ); for ( int i = 0; i < oldItemList.size(); ++i ) { - createAnnotationFromXml( oldItemList.at( i ).toElement(), context ); + createAnnotationFromElement( oldItemList.at( i ).toElement() ); } oldItemList = element.elementsByTagName( QStringLiteral( "FormAnnotationItem" ) ); for ( int i = 0; i < oldItemList.size(); ++i ) { - createAnnotationFromXml( oldItemList.at( i ).toElement(), context ); + createAnnotationFromElement( oldItemList.at( i ).toElement() ); } oldItemList = element.elementsByTagName( QStringLiteral( "HtmlAnnotationItem" ) ); for ( int i = 0; i < oldItemList.size(); ++i ) { - createAnnotationFromXml( oldItemList.at( i ).toElement(), context ); + createAnnotationFromElement( oldItemList.at( i ).toElement() ); } oldItemList = element.elementsByTagName( QStringLiteral( "SVGAnnotationItem" ) ); for ( int i = 0; i < oldItemList.size(); ++i ) { - createAnnotationFromXml( oldItemList.at( i ).toElement(), context ); + createAnnotationFromElement( oldItemList.at( i ).toElement() ); } } return result; } +std::unique_ptr QgsAnnotationManager::convertToAnnotationItem( QgsAnnotation *annotation ) +{ + Q_UNUSED( annotation ); + return nullptr; +} + QDomElement QgsAnnotationManager::writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const { QDomElement annotationsElem = doc.createElement( QStringLiteral( "Annotations" ) ); @@ -169,12 +212,12 @@ bool QgsAnnotationManager::accept( QgsStyleEntityVisitorInterface *visitor ) con return true; } -void QgsAnnotationManager::createAnnotationFromXml( const QDomElement &element, const QgsReadWriteContext &context ) +QgsAnnotation *QgsAnnotationManager::createAnnotationFromXml( const QDomElement &element, const QgsReadWriteContext &context ) { QString type = element.tagName(); QgsAnnotation *annotation = QgsApplication::annotationRegistry()->create( type ); if ( !annotation ) - return; + return nullptr; annotation->readXml( element, context ); @@ -183,5 +226,5 @@ void QgsAnnotationManager::createAnnotationFromXml( const QDomElement &element, annotation->setMapPositionCrs( mProject->crs() ); } - addAnnotation( annotation ); + return annotation; } diff --git a/src/core/annotations/qgsannotationmanager.h b/src/core/annotations/qgsannotationmanager.h index 8e2265d913e1..9c83aecc2442 100644 --- a/src/core/annotations/qgsannotationmanager.h +++ b/src/core/annotations/qgsannotationmanager.h @@ -25,6 +25,8 @@ class QgsReadWriteContext; class QgsProject; class QgsAnnotation; class QgsStyleEntityVisitorInterface; +class QgsAnnotationLayer; +class QgsAnnotationItem; /** * \ingroup core @@ -100,6 +102,21 @@ class CORE_EXPORT QgsAnnotationManager : public QObject */ bool readXml( const QDomElement &element, const QgsReadWriteContext &context ); + /** + * Reads the manager's state from a DOM element, restoring annotations + * present in the XML document. + * + * Annotations which can be safely converted to QgsAnnotationItem subclasses will + * be automatically converted to those, and stored in the specified annotation \a layer. + * + * \note Not available in Python bindings + * + * \see writeXml() + * \since QGIS 3.40 + */ + bool readXmlAndUpgradeToAnnotationLayerItems( const QDomElement &element, const QgsReadWriteContext &context, + QgsAnnotationLayer *layer ) SIP_SKIP; + /** * Returns a DOM element representing the state of the manager. * \see readXml() @@ -130,11 +147,14 @@ class CORE_EXPORT QgsAnnotationManager : public QObject private: + bool readXmlPrivate( const QDomElement &element, const QgsReadWriteContext &context, QgsAnnotationLayer *layer ); + static std::unique_ptr< QgsAnnotationItem > convertToAnnotationItem( QgsAnnotation *annotation ); + QgsProject *mProject = nullptr; QList< QgsAnnotation * > mAnnotations; - void createAnnotationFromXml( const QDomElement &element, const QgsReadWriteContext &context ); + QgsAnnotation *createAnnotationFromXml( const QDomElement &element, const QgsReadWriteContext &context ); }; diff --git a/src/core/project/qgsproject.cpp b/src/core/project/qgsproject.cpp index 8e5e395f30f8..aef9cb2fb4fe 100644 --- a/src/core/project/qgsproject.cpp +++ b/src/core/project/qgsproject.cpp @@ -2499,7 +2499,7 @@ bool QgsProject::readProjectFile( const QString &filename, Qgis::ProjectReadFlag emit labelingEngineSettingsChanged(); profile.switchTask( tr( "Loading annotations" ) ); - mAnnotationManager->readXml( doc->documentElement(), context ); + mAnnotationManager->readXmlAndUpgradeToAnnotationLayerItems( doc->documentElement(), context, mMainAnnotationLayer ); if ( !( flags & Qgis::ProjectReadFlag::DontLoadLayouts ) ) { profile.switchTask( tr( "Loading layouts" ) ); From 830cb8d3518dcde0131838b7b2a89627334d0db1 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 16 Aug 2024 12:30:51 +1000 Subject: [PATCH 02/15] Auto upgrade old SVG annotations to picture annotation items on project load --- src/core/annotations/qgsannotationmanager.cpp | 102 ++++++++++++++++-- src/core/annotations/qgsannotationmanager.h | 8 +- src/core/project/qgsproject.cpp | 2 +- 3 files changed, 100 insertions(+), 12 deletions(-) diff --git a/src/core/annotations/qgsannotationmanager.cpp b/src/core/annotations/qgsannotationmanager.cpp index 0ef1f23872b5..604c185b3637 100644 --- a/src/core/annotations/qgsannotationmanager.cpp +++ b/src/core/annotations/qgsannotationmanager.cpp @@ -21,6 +21,10 @@ #include "qgsstyleentityvisitor.h" #include "qgsannotationitem.h" #include "qgsannotationlayer.h" +#include "qgssvgannotation.h" +#include "qgsannotationpictureitem.h" +#include "qgsmarkersymbol.h" +#include "qgsfillsymbol.h" QgsAnnotationManager::QgsAnnotationManager( QgsProject *project ) : QObject( project ) @@ -89,21 +93,21 @@ QList QgsAnnotationManager::cloneAnnotations() const bool QgsAnnotationManager::readXml( const QDomElement &element, const QgsReadWriteContext &context ) { - return readXmlPrivate( element, context, nullptr ); + return readXmlPrivate( element, context, nullptr, QgsCoordinateTransformContext() ); } -bool QgsAnnotationManager::readXmlAndUpgradeToAnnotationLayerItems( const QDomElement &element, const QgsReadWriteContext &context, QgsAnnotationLayer *layer ) +bool QgsAnnotationManager::readXmlAndUpgradeToAnnotationLayerItems( const QDomElement &element, const QgsReadWriteContext &context, QgsAnnotationLayer *layer, const QgsCoordinateTransformContext &transformContext ) { - return readXmlPrivate( element, context, layer ); + return readXmlPrivate( element, context, layer, transformContext ); } -bool QgsAnnotationManager::readXmlPrivate( const QDomElement &element, const QgsReadWriteContext &context, QgsAnnotationLayer *layer ) +bool QgsAnnotationManager::readXmlPrivate( const QDomElement &element, const QgsReadWriteContext &context, QgsAnnotationLayer *layer, const QgsCoordinateTransformContext &transformContext ) { clear(); //restore each annotation bool result = true; - auto createAnnotationFromElement = [this, &context, layer]( const QDomElement & element ) + auto createAnnotationFromElement = [this, &context, layer, &transformContext]( const QDomElement & element ) { std::unique_ptr< QgsAnnotation > annotation( createAnnotationFromXml( element, context ) ); if ( !annotation ) @@ -111,7 +115,7 @@ bool QgsAnnotationManager::readXmlPrivate( const QDomElement &element, const Qgs if ( layer ) { - std::unique_ptr< QgsAnnotationItem > annotationItem = convertToAnnotationItem( annotation.get() ); + std::unique_ptr< QgsAnnotationItem > annotationItem = convertToAnnotationItem( annotation.get(), layer, transformContext ); if ( annotationItem ) { layer->addItem( annotationItem.release() ); @@ -165,9 +169,91 @@ bool QgsAnnotationManager::readXmlPrivate( const QDomElement &element, const Qgs return result; } -std::unique_ptr QgsAnnotationManager::convertToAnnotationItem( QgsAnnotation *annotation ) +std::unique_ptr QgsAnnotationManager::convertToAnnotationItem( QgsAnnotation *annotation, QgsAnnotationLayer *layer, const QgsCoordinateTransformContext &transformContext ) { - Q_UNUSED( annotation ); + auto setCommonProperties = [layer, &transformContext]( const QgsAnnotation * source, QgsAnnotationItem * destination ) -> bool + { + destination->setEnabled( source->isVisible() ); + if ( source->hasFixedMapPosition() ) + { + QgsPointXY mapPosition = source->mapPosition(); + QgsCoordinateTransform transform( source->mapPositionCrs(), layer->crs(), transformContext ); + try + { + transform.transform( mapPosition ); + } + catch ( QgsCsException & ) + { + QgsDebugError( QStringLiteral( "Error transforming annotation position" ) ); + return false; + } + + destination->setCalloutAnchor( QgsGeometry::fromPointXY( mapPosition ) ); + + std::unique_ptr< QgsBalloonCallout > callout = std::make_unique< QgsBalloonCallout >(); + if ( QgsFillSymbol *fill = source->fillSymbol() ) + callout->setFillSymbol( fill->clone() ); + + if ( QgsMarkerSymbol *marker = source->markerSymbol() ) + callout->setMarkerSymbol( marker->clone() ); + callout->setMargins( source->contentsMargin() ); + callout->setMarginsUnit( Qgis::RenderUnit::Millimeters ); + destination->setCallout( callout.release() ); + } + + if ( source->mapLayer() ) + layer->setLinkedVisibilityLayer( source->mapLayer() ); + + return true; + + }; + + if ( const QgsSvgAnnotation *svg = dynamic_cast< const QgsSvgAnnotation *>( annotation ) ) + { + QgsPointXY mapPosition = svg->mapPosition(); + QgsCoordinateTransform transform( svg->mapPositionCrs(), layer->crs(), transformContext ); + try + { + transform.transform( mapPosition ); + } + catch ( QgsCsException & ) + { + QgsDebugError( QStringLiteral( "Error transforming annotation position" ) ); + } + + std::unique_ptr< QgsAnnotationPictureItem > item = std::make_unique< QgsAnnotationPictureItem >( Qgis::PictureFormat::SVG, + svg->filePath(), QgsRectangle::fromCenterAndSize( mapPosition, 1, 1 ) ); + if ( !setCommonProperties( annotation, item.get() ) ) + return nullptr; + + const QgsMargins margins = svg->contentsMargin(); + item->setFixedSize( QSizeF( svg->frameSizeMm().width() - margins.left() - margins.right(), + svg->frameSizeMm().height() - margins.top() - margins.bottom() ) ); + item->setFixedSizeUnit( Qgis::RenderUnit::Millimeters ); + + if ( svg->hasFixedMapPosition() ) + { + item->setPlacementMode( Qgis::AnnotationPlacementMode::FixedSize ); + + item->setOffsetFromCallout( QSizeF( svg->frameOffsetFromReferencePointMm().x() + margins.left(), + svg->frameOffsetFromReferencePointMm().y() + margins.top() ) ); + item->setOffsetFromCalloutUnit( Qgis::RenderUnit::Millimeters ); + } + else + { + item->setPlacementMode( Qgis::AnnotationPlacementMode::RelativeToMapFrame ); + item->setBounds( QgsRectangle( svg->relativePosition().x(), svg->relativePosition().y(), + svg->relativePosition().x(), svg->relativePosition().y() ) ); + if ( QgsFillSymbol *fill = svg->fillSymbol() ) + { + item->setBackgroundEnabled( true ); + item->setBackgroundSymbol( fill->clone() ); + } + } + + return item; + } + return nullptr; } diff --git a/src/core/annotations/qgsannotationmanager.h b/src/core/annotations/qgsannotationmanager.h index 9c83aecc2442..5e6c236b5ac1 100644 --- a/src/core/annotations/qgsannotationmanager.h +++ b/src/core/annotations/qgsannotationmanager.h @@ -27,6 +27,7 @@ class QgsAnnotation; class QgsStyleEntityVisitorInterface; class QgsAnnotationLayer; class QgsAnnotationItem; +class QgsCoordinateTransformContext; /** * \ingroup core @@ -115,7 +116,7 @@ class CORE_EXPORT QgsAnnotationManager : public QObject * \since QGIS 3.40 */ bool readXmlAndUpgradeToAnnotationLayerItems( const QDomElement &element, const QgsReadWriteContext &context, - QgsAnnotationLayer *layer ) SIP_SKIP; + QgsAnnotationLayer *layer, const QgsCoordinateTransformContext &transformContext ) SIP_SKIP; /** * Returns a DOM element representing the state of the manager. @@ -147,8 +148,9 @@ class CORE_EXPORT QgsAnnotationManager : public QObject private: - bool readXmlPrivate( const QDomElement &element, const QgsReadWriteContext &context, QgsAnnotationLayer *layer ); - static std::unique_ptr< QgsAnnotationItem > convertToAnnotationItem( QgsAnnotation *annotation ); + bool readXmlPrivate( const QDomElement &element, const QgsReadWriteContext &context, QgsAnnotationLayer *layer, const QgsCoordinateTransformContext &transformContext ); + static std::unique_ptr< QgsAnnotationItem > convertToAnnotationItem( QgsAnnotation *annotation, QgsAnnotationLayer *layer, + const QgsCoordinateTransformContext &transformContext ); QgsProject *mProject = nullptr; diff --git a/src/core/project/qgsproject.cpp b/src/core/project/qgsproject.cpp index aef9cb2fb4fe..9eef91904f9a 100644 --- a/src/core/project/qgsproject.cpp +++ b/src/core/project/qgsproject.cpp @@ -2499,7 +2499,7 @@ bool QgsProject::readProjectFile( const QString &filename, Qgis::ProjectReadFlag emit labelingEngineSettingsChanged(); profile.switchTask( tr( "Loading annotations" ) ); - mAnnotationManager->readXmlAndUpgradeToAnnotationLayerItems( doc->documentElement(), context, mMainAnnotationLayer ); + mAnnotationManager->readXmlAndUpgradeToAnnotationLayerItems( doc->documentElement(), context, mMainAnnotationLayer, mTransformContext ); if ( !( flags & Qgis::ProjectReadFlag::DontLoadLayouts ) ) { profile.switchTask( tr( "Loading layouts" ) ); From 08481792eb58c87e674fa8e859bc28a1f6ea498d Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 19 Aug 2024 14:18:40 +1000 Subject: [PATCH 03/15] Add tests --- tests/src/python/test_qgsannotation.py | 68 ++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/tests/src/python/test_qgsannotation.py b/tests/src/python/test_qgsannotation.py index e08825145a6d..a10ae6274ee3 100644 --- a/tests/src/python/test_qgsannotation.py +++ b/tests/src/python/test_qgsannotation.py @@ -11,9 +11,13 @@ __date__ = '24/1/2017' __copyright__ = 'Copyright 2017, The QGIS Project' +import os +import tempfile + from qgis.PyQt.QtCore import QPointF, QRectF, QSize, QSizeF from qgis.PyQt.QtGui import QColor, QImage, QPainter, QTextDocument from qgis.core import ( + Qgis, QgsCoordinateReferenceSystem, QgsFeature, QgsFillSymbol, @@ -30,6 +34,8 @@ QgsSvgAnnotation, QgsTextAnnotation, QgsVectorLayer, + QgsAnnotationPictureItem, + QgsBalloonCallout ) from qgis.gui import QgsFormAnnotation import unittest @@ -81,6 +87,68 @@ def testTextAnnotationInLayout(self): a.setDocument(doc) self.assertTrue(self.renderAnnotationInLayout('text_annotation_in_layout', a)) + def test_svg_annotation_project_upgrade(self): + """ + Test that svg annotations are upgraded to annotation layers when loading projects + """ + 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.setHasFixedMapPosition(True) + a.setMapPosition(QgsPointXY(QPointF(20, 30))) + a.setMapPositionCrs(QgsCoordinateReferenceSystem('EPSG:4326')) + a.setFrameOffsetFromReferencePointMm( + QPointF(40 / 3.7795275, 50 / 3.7795275)) + svg = TEST_DATA_DIR + "/sample_svg.svg" + a.setFilePath(svg) + + b = QgsSvgAnnotation() + b.fillSymbol().symbolLayer(0).setStrokeColor(QColor(0, 0, 0)) + b.markerSymbol().symbolLayer(0).setStrokeColor(QColor(0, 0, 0)) + b.setFrameSizeMm(QSizeF(300 / 3.7795275, 200 / 3.7795275)) + b.setRelativePosition(QPointF(0.2, 0.7)) + b.setHasFixedMapPosition(False) + svg = TEST_DATA_DIR + "/sample_svg.svg" + b.setFilePath(svg) + + p = QgsProject() + p.annotationManager().addAnnotation(a) + p.annotationManager().addAnnotation(b) + + p2 = QgsProject() + with tempfile.TemporaryDirectory() as temp_dir: + path = os.path.join(temp_dir, 'test_project.qgs') + p.write(path) + p2.read(path) + + # should be no annotations in upgraded project + self.assertFalse(p2.annotationManager().annotations()) + # annotation layer should contain picture items + items = p2.mainAnnotationLayer().items() + self.assertEqual(len(items), 2) + + item_a = [i for _, i in items.items() if not i.calloutAnchor().isEmpty()][0] + item_b = [i for _, i in items.items() if i.calloutAnchor().isEmpty()][0] + self.assertIsInstance(item_a, QgsAnnotationPictureItem) + self.assertIsInstance(item_b, QgsAnnotationPictureItem) + + self.assertEqual(item_a.calloutAnchor().asWkt(), 'Point (20 30)') + self.assertEqual(item_a.placementMode(), Qgis.AnnotationPlacementMode.FixedSize) + self.assertIsInstance(item_a.callout(), + QgsBalloonCallout) + self.assertAlmostEqual(item_a.fixedSize().width(), 79.375, 1) + self.assertAlmostEqual(item_a.fixedSize().height(), 52.9166, 1) + self.assertAlmostEqual(item_a.offsetFromCallout().width(), 10.5833, 1) + self.assertAlmostEqual(item_a.offsetFromCallout().height(), 13.229, 1) + + self.assertIsNone(item_b.callout()) + self.assertEqual(item_b.placementMode(), Qgis.AnnotationPlacementMode.RelativeToMapFrame) + self.assertAlmostEqual(item_b.fixedSize().width(), 79.375, 1) + self.assertAlmostEqual(item_b.fixedSize().height(), 52.9166, 1) + self.assertAlmostEqual(item_b.bounds().center().x(), 0.2, 3) + self.assertAlmostEqual(item_b.bounds().center().y(), 0.7, 3) + def testSvgAnnotation(self): """ test rendering a svg annotation""" a = QgsSvgAnnotation() From be359f41d8dc7465d72aa7476c6f341144fea67c Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 19 Aug 2024 14:24:22 +1000 Subject: [PATCH 04/15] Remove action for creating old SVG annotation items The new Picture annotation item should be used instead --- src/app/CMakeLists.txt | 1 - src/app/maptools/qgsappmaptools.cpp | 3 --- src/app/maptools/qgsappmaptools.h | 1 - src/app/qgisapp.cpp | 14 ------------ src/app/qgisapp.h | 1 - src/app/qgsmaptoolsvgannotation.cpp | 31 -------------------------- src/app/qgsmaptoolsvgannotation.h | 34 ----------------------------- src/ui/qgisapp.ui | 24 +++++--------------- 8 files changed, 5 insertions(+), 104 deletions(-) delete mode 100644 src/app/qgsmaptoolsvgannotation.cpp delete mode 100644 src/app/qgsmaptoolsvgannotation.h diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 50f31d9b5a71..cb10cc3a4768 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -104,7 +104,6 @@ set(QGIS_APP_SRCS qgsmaptoolsimplify.cpp qgsmaptoolsplitfeatures.cpp qgsmaptoolsplitparts.cpp - qgsmaptoolsvgannotation.cpp qgsmaptooltextannotation.cpp annotations/qgsannotationitempropertieswidget.cpp diff --git a/src/app/maptools/qgsappmaptools.cpp b/src/app/maptools/qgsappmaptools.cpp index bef70d7a6c10..1831072e88ef 100644 --- a/src/app/maptools/qgsappmaptools.cpp +++ b/src/app/maptools/qgsappmaptools.cpp @@ -25,11 +25,9 @@ #include "qgsmeasuretool.h" #include "qgsmaptooltextannotation.h" #include "qgsmaptoolhtmlannotation.h" -#include "qgsmaptoolannotation.h" #include "qgsmaptoolmeasureangle.h" #include "qgsmaptoolmeasurebearing.h" #include "qgsmaptoolformannotation.h" -#include "qgsmaptoolsvgannotation.h" #include "qgsmaptoolrotatefeature.h" #include "qgsmaptoolscalefeature.h" #include "qgsmaptoolmovefeature.h" @@ -75,7 +73,6 @@ QgsAppMapTools::QgsAppMapTools( QgsMapCanvas *canvas, QgsAdvancedDigitizingDockW mTools.insert( Tool::TextAnnotation, new QgsMapToolTextAnnotation( canvas ) ); mTools.insert( Tool::FormAnnotation, new QgsMapToolFormAnnotation( canvas ) ); mTools.insert( Tool::HtmlAnnotation, new QgsMapToolHtmlAnnotation( canvas ) ); - mTools.insert( Tool::SvgAnnotation, new QgsMapToolSvgAnnotation( canvas ) ); mTools.insert( Tool::AddFeature, new QgsMapToolAddFeature( canvas, cadDock, QgsMapToolCapture::CaptureNone ) ); mTools.insert( Tool::MoveFeature, new QgsMapToolMoveFeature( canvas, QgsMapToolMoveFeature::Move ) ); mTools.insert( Tool::MoveFeatureCopy, new QgsMapToolMoveFeature( canvas, QgsMapToolMoveFeature::CopyMove ) ); diff --git a/src/app/maptools/qgsappmaptools.h b/src/app/maptools/qgsappmaptools.h index 91e55befa4fa..cb329d476c46 100644 --- a/src/app/maptools/qgsappmaptools.h +++ b/src/app/maptools/qgsappmaptools.h @@ -68,7 +68,6 @@ class QgsAppMapTools Annotation, // Unused FormAnnotation, HtmlAnnotation, - SvgAnnotation, TextAnnotation, PinLabels, ShowHideLabels, diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 8d9f590a4d9e..9c880287c93d 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -3024,7 +3024,6 @@ void QgisApp::createActions() connect( mActionTextAnnotation, &QAction::triggered, this, &QgisApp::addTextAnnotation ); connect( mActionFormAnnotation, &QAction::triggered, this, &QgisApp::addFormAnnotation ); connect( mActionHtmlAnnotation, &QAction::triggered, this, &QgisApp::addHtmlAnnotation ); - connect( mActionSvgAnnotation, &QAction::triggered, this, &QgisApp::addSvgAnnotation ); connect( mActionLabeling, &QAction::triggered, this, &QgisApp::labeling ); mStatisticalSummaryDockWidget->setToggleVisibilityAction( mActionStatisticalSummary ); connect( mActionManage3DMapViews, &QAction::triggered, this, &QgisApp::show3DMapViewsManager ); @@ -3970,7 +3969,6 @@ void QgisApp::createToolBars() bt->addAction( mActionTextAnnotation ); bt->addAction( mActionFormAnnotation ); bt->addAction( mActionHtmlAnnotation ); - bt->addAction( mActionSvgAnnotation ); QAction *defAnnotationAction = mActionTextAnnotation; switch ( settings.value( QStringLiteral( "UI/annotationTool" ), 0 ).toInt() ) @@ -3984,9 +3982,6 @@ void QgisApp::createToolBars() case 2: defAnnotationAction = mActionHtmlAnnotation; break; - case 3: - defAnnotationAction = mActionSvgAnnotation; - break; } bt->setDefaultAction( defAnnotationAction ); QAction *annotationAction = mAnnotationsToolBar->addWidget( bt ); @@ -4364,7 +4359,6 @@ void QgisApp::setTheme( const QString &themeName ) mActionAddToOverview->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionInOverview.svg" ) ) ); mActionFormAnnotation->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionFormAnnotation.svg" ) ) ); mActionHtmlAnnotation->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionHtmlAnnotation.svg" ) ) ); - mActionSvgAnnotation->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSvgAnnotation.svg" ) ) ); mActionTextAnnotation->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionTextAnnotation.svg" ) ) ); mActionLabeling->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionLabeling.svg" ) ) ); mActionShowPinnedLabels->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionShowPinnedLabels.svg" ) ) ); @@ -4588,7 +4582,6 @@ void QgisApp::setupCanvasTools() mMapTools->mapTool( QgsAppMapTools::TextAnnotation )->setAction( mActionTextAnnotation ); mMapTools->mapTool( QgsAppMapTools::FormAnnotation )->setAction( mActionFormAnnotation ); mMapTools->mapTool( QgsAppMapTools::HtmlAnnotation )->setAction( mActionHtmlAnnotation ); - mMapTools->mapTool( QgsAppMapTools::SvgAnnotation )->setAction( mActionSvgAnnotation ); mMapTools->mapTool( QgsAppMapTools::AddFeature )->setAction( mActionAddFeature ); mMapTools->mapTool( QgsAppMapTools::MoveFeature )->setAction( mActionMoveFeature ); mMapTools->mapTool( QgsAppMapTools::MoveFeatureCopy )->setAction( mActionMoveFeatureCopy ); @@ -7945,11 +7938,6 @@ void QgisApp::addTextAnnotation() mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::TextAnnotation ) ); } -void QgisApp::addSvgAnnotation() -{ - mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::SvgAnnotation ) ); -} - void QgisApp::reprojectAnnotations() { const auto annotations = annotationItems(); @@ -17033,8 +17021,6 @@ void QgisApp::toolButtonActionTriggered( QAction *action ) settings.setValue( QStringLiteral( "UI/annotationTool" ), 1 ); else if ( action == mActionHtmlAnnotation ) settings.setValue( QStringLiteral( "UI/annotationTool" ), 2 ); - else if ( action == mActionSvgAnnotation ) - settings.setValue( QStringLiteral( "UI/annotationTool" ), 3 ); else if ( action == mActionNewSpatiaLiteLayer ) settings.setValue( QStringLiteral( "UI/defaultNewLayer" ), 0 ); else if ( action == mActionNewVectorLayer ) diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h index bf8511ddad11..023a1356e2a1 100644 --- a/src/app/qgisapp.h +++ b/src/app/qgisapp.h @@ -1949,7 +1949,6 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow void addFormAnnotation(); void addTextAnnotation(); void addHtmlAnnotation(); - void addSvgAnnotation(); void reprojectAnnotations(); //! Alerts user when commit errors occurred diff --git a/src/app/qgsmaptoolsvgannotation.cpp b/src/app/qgsmaptoolsvgannotation.cpp deleted file mode 100644 index 0b21321d37e7..000000000000 --- a/src/app/qgsmaptoolsvgannotation.cpp +++ /dev/null @@ -1,31 +0,0 @@ -/*************************************************************************** - qgsmaptoolsvgannotation.cpp - --------------------------- - begin : November, 2012 - copyright : (C) 2012 by Marco Hugentobler - email : marco dot hugentobler at sourcepole dot ch - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#include "qgsmaptoolsvgannotation.h" -#include "qgssvgannotation.h" -#include "qgsproject.h" -#include - -QgsMapToolSvgAnnotation::QgsMapToolSvgAnnotation( QgsMapCanvas *canvas ): QgsMapToolAnnotation( canvas ) -{ - -} - -QgsAnnotation *QgsMapToolSvgAnnotation::createItem() const -{ - return new QgsSvgAnnotation(); -} diff --git a/src/app/qgsmaptoolsvgannotation.h b/src/app/qgsmaptoolsvgannotation.h deleted file mode 100644 index 3f2041c369ea..000000000000 --- a/src/app/qgsmaptoolsvgannotation.h +++ /dev/null @@ -1,34 +0,0 @@ -/*************************************************************************** - qgsmaptoolsvgannotation.h - ------------------------- - begin : November, 2012 - copyright : (C) 2012 by Marco Hugentobler - email : marco dot hugentobler at sourcepole dot ch - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#ifndef QGSMAPTOOLSVGANNOTATION_H -#define QGSMAPTOOLSVGANNOTATION_H - -#include "qgsmaptoolannotation.h" -#include "qgis_app.h" - -class APP_EXPORT QgsMapToolSvgAnnotation: public QgsMapToolAnnotation -{ - Q_OBJECT - - public: - QgsMapToolSvgAnnotation( QgsMapCanvas *canvas ); - protected: - QgsAnnotation *createItem() const override; -}; - -#endif // QGSMAPTOOLSVGANNOTATION_H diff --git a/src/ui/qgisapp.ui b/src/ui/qgisapp.ui index 287aefb431fd..9a48bd4b20b4 100644 --- a/src/ui/qgisapp.ui +++ b/src/ui/qgisapp.ui @@ -17,7 +17,7 @@ 0 0 1368 - 27 + 23 @@ -352,7 +352,6 @@ - @@ -894,7 +893,7 @@ Ctrl+Q - QAction::QuitRole + QAction::MenuRole::QuitRole @@ -1892,7 +1891,7 @@ Shift+O to turn segments into straight or curve lines. &Options… - QAction::NoRole + QAction::MenuRole::NoRole @@ -1913,7 +1912,7 @@ Shift+O to turn segments into straight or curve lines. Keyboard Shortcuts… - QAction::NoRole + QAction::MenuRole::NoRole @@ -1983,7 +1982,7 @@ Shift+O to turn segments into straight or curve lines. About - QAction::AboutRole + QAction::MenuRole::AboutRole @@ -2412,18 +2411,6 @@ Acts on the currently active layer only. Duplicate Layer(s) - - - true - - - - :/images/themes/default/mActionSvgAnnotation.svg:/images/themes/default/mActionSvgAnnotation.svg - - - SVG Annotation - - @@ -3389,7 +3376,6 @@ Shows placeholders for labels which could not be placed, e.g. due to overlaps wi - From 24e6cc1ea0b34749d0ad33f54b0d89cd54877cb3 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 19 Aug 2024 14:29:06 +1000 Subject: [PATCH 05/15] Fix crash when deleting nodes --- src/gui/annotations/qgsmaptoolmodifyannotation.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gui/annotations/qgsmaptoolmodifyannotation.cpp b/src/gui/annotations/qgsmaptoolmodifyannotation.cpp index 2d0532ebe8c1..b4e3ff4f1078 100644 --- a/src/gui/annotations/qgsmaptoolmodifyannotation.cpp +++ b/src/gui/annotations/qgsmaptoolmodifyannotation.cpp @@ -795,6 +795,9 @@ void QgsMapToolModifyAnnotation::setHoveredItemFromPoint( const QgsPointXY &mapP double currentNodeDistance = std::numeric_limits< double >::max(); mHoveredItemNodesSpatialIndex->intersects( searchRect, [&hoveredNode, ¤tNodeDistance, &mapPoint, this]( int index )-> bool { + if ( index >= mHoveredItemNodes.size() ) + return false; + const QgsAnnotationItemNode &thisNode = mHoveredItemNodes.at( index ); const double nodeDistance = thisNode.point().sqrDist( mapPoint ); if ( nodeDistance < currentNodeDistance ) From 855529b1cdb08ab02b45e00010031a06df135cec Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 19 Aug 2024 14:53:17 +1000 Subject: [PATCH 06/15] Add auto-upgrade for old text annotations --- .../qgsannotationrectangletextitem.sip.in | 56 ++ .../qgsannotationrectangletextitem.sip.in | 56 ++ src/core/annotations/qgsannotationmanager.cpp | 53 +- .../qgsannotationrectangletextitem.cpp | 607 +++++++++++++++--- .../qgsannotationrectangletextitem.h | 55 ++ 5 files changed, 735 insertions(+), 92 deletions(-) diff --git a/python/PyQt6/core/auto_generated/annotations/qgsannotationrectangletextitem.sip.in b/python/PyQt6/core/auto_generated/annotations/qgsannotationrectangletextitem.sip.in index 01f9153e5b44..7af569f5bf97 100644 --- a/python/PyQt6/core/auto_generated/annotations/qgsannotationrectangletextitem.sip.in +++ b/python/PyQt6/core/auto_generated/annotations/qgsannotationrectangletextitem.sip.in @@ -55,6 +55,8 @@ Creates a new rectangle text annotation item. virtual QgsRectangle boundingBox() const; + virtual QgsRectangle boundingBox( QgsRenderContext &context ) const; + QgsRectangle bounds() const; %Docstring @@ -86,6 +88,60 @@ Returns the text rendered by the item. Sets the ``text`` rendered by the item. .. seealso:: :py:func:`text` +%End + + Qgis::AnnotationPlacementMode placementMode() const; +%Docstring +Returns the placement mode for the text. + +.. seealso:: :py:func:`setPlacementMode` +%End + + void setPlacementMode( Qgis::AnnotationPlacementMode mode ); +%Docstring +Sets the placement ``mode`` for the text. + +.. seealso:: :py:func:`placementMode` +%End + + QSizeF fixedSize() const; +%Docstring +Returns the fixed size to use for the text, when the :py:func:`~QgsAnnotationRectangleTextItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. + +Units are retrieved via :py:func:`~QgsAnnotationRectangleTextItem.fixedSizeUnit` + +.. seealso:: :py:func:`setFixedSize` + +.. seealso:: :py:func:`fixedSizeUnit` +%End + + void setFixedSize( const QSizeF &size ); +%Docstring +Sets the fixed ``size`` to use for the text, when the :py:func:`~QgsAnnotationRectangleTextItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. + +Units are set via :py:func:`~QgsAnnotationRectangleTextItem.setFixedSizeUnit` + +.. seealso:: :py:func:`fixedSize` + +.. seealso:: :py:func:`setFixedSizeUnit` +%End + + Qgis::RenderUnit fixedSizeUnit() const; +%Docstring +Returns the units to use for fixed text sizes, when the :py:func:`~QgsAnnotationRectangleTextItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. + +.. seealso:: :py:func:`setFixedSizeUnit` + +.. seealso:: :py:func:`fixedSize` +%End + + void setFixedSizeUnit( Qgis::RenderUnit unit ); +%Docstring +Sets the ``unit`` to use for fixed text sizes, when the :py:func:`~QgsAnnotationRectangleTextItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. + +.. seealso:: :py:func:`fixedSizeUnit` + +.. seealso:: :py:func:`setFixedSize` %End QgsTextFormat format() const; diff --git a/python/core/auto_generated/annotations/qgsannotationrectangletextitem.sip.in b/python/core/auto_generated/annotations/qgsannotationrectangletextitem.sip.in index 01f9153e5b44..7af569f5bf97 100644 --- a/python/core/auto_generated/annotations/qgsannotationrectangletextitem.sip.in +++ b/python/core/auto_generated/annotations/qgsannotationrectangletextitem.sip.in @@ -55,6 +55,8 @@ Creates a new rectangle text annotation item. virtual QgsRectangle boundingBox() const; + virtual QgsRectangle boundingBox( QgsRenderContext &context ) const; + QgsRectangle bounds() const; %Docstring @@ -86,6 +88,60 @@ Returns the text rendered by the item. Sets the ``text`` rendered by the item. .. seealso:: :py:func:`text` +%End + + Qgis::AnnotationPlacementMode placementMode() const; +%Docstring +Returns the placement mode for the text. + +.. seealso:: :py:func:`setPlacementMode` +%End + + void setPlacementMode( Qgis::AnnotationPlacementMode mode ); +%Docstring +Sets the placement ``mode`` for the text. + +.. seealso:: :py:func:`placementMode` +%End + + QSizeF fixedSize() const; +%Docstring +Returns the fixed size to use for the text, when the :py:func:`~QgsAnnotationRectangleTextItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. + +Units are retrieved via :py:func:`~QgsAnnotationRectangleTextItem.fixedSizeUnit` + +.. seealso:: :py:func:`setFixedSize` + +.. seealso:: :py:func:`fixedSizeUnit` +%End + + void setFixedSize( const QSizeF &size ); +%Docstring +Sets the fixed ``size`` to use for the text, when the :py:func:`~QgsAnnotationRectangleTextItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. + +Units are set via :py:func:`~QgsAnnotationRectangleTextItem.setFixedSizeUnit` + +.. seealso:: :py:func:`fixedSize` + +.. seealso:: :py:func:`setFixedSizeUnit` +%End + + Qgis::RenderUnit fixedSizeUnit() const; +%Docstring +Returns the units to use for fixed text sizes, when the :py:func:`~QgsAnnotationRectangleTextItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. + +.. seealso:: :py:func:`setFixedSizeUnit` + +.. seealso:: :py:func:`fixedSize` +%End + + void setFixedSizeUnit( Qgis::RenderUnit unit ); +%Docstring +Sets the ``unit`` to use for fixed text sizes, when the :py:func:`~QgsAnnotationRectangleTextItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. + +.. seealso:: :py:func:`fixedSizeUnit` + +.. seealso:: :py:func:`setFixedSize` %End QgsTextFormat format() const; diff --git a/src/core/annotations/qgsannotationmanager.cpp b/src/core/annotations/qgsannotationmanager.cpp index 604c185b3637..bbeb2d0d4dd6 100644 --- a/src/core/annotations/qgsannotationmanager.cpp +++ b/src/core/annotations/qgsannotationmanager.cpp @@ -23,6 +23,7 @@ #include "qgsannotationlayer.h" #include "qgssvgannotation.h" #include "qgsannotationpictureitem.h" +#include "qgsannotationrectangletextitem.h" #include "qgsmarkersymbol.h" #include "qgsfillsymbol.h" @@ -180,7 +181,7 @@ std::unique_ptr QgsAnnotationManager::convertToAnnotationItem QgsCoordinateTransform transform( source->mapPositionCrs(), layer->crs(), transformContext ); try { - transform.transform( mapPosition ); + mapPosition = transform.transform( mapPosition ); } catch ( QgsCsException & ) { @@ -253,6 +254,56 @@ std::unique_ptr QgsAnnotationManager::convertToAnnotationItem return item; } + else if ( const QgsTextAnnotation *text = dynamic_cast< const QgsTextAnnotation *>( annotation ) ) + { + QgsPointXY mapPosition = text->mapPosition(); + QgsCoordinateTransform transform( text->mapPositionCrs(), layer->crs(), transformContext ); + try + { + transform.transform( mapPosition ); + } + catch ( QgsCsException & ) + { + QgsDebugError( QStringLiteral( "Error transforming annotation position" ) ); + } + + std::unique_ptr< QgsAnnotationRectangleTextItem > item = std::make_unique< QgsAnnotationRectangleTextItem >( text->document()->toHtml(), QgsRectangle::fromCenterAndSize( mapPosition, 1, 1 ) ); + if ( !setCommonProperties( annotation, item.get() ) ) + return nullptr; + + QgsTextFormat format = item->format(); + format.setAllowHtmlFormatting( true ); + item->setFormat( format ); + + const QgsMargins margins = text->contentsMargin(); + item->setFixedSize( QSizeF( text->frameSizeMm().width() - margins.left() - margins.right(), + text->frameSizeMm().height() - margins.top() - margins.bottom() ) ); + item->setFixedSizeUnit( Qgis::RenderUnit::Millimeters ); + + if ( text->hasFixedMapPosition() ) + { + item->setPlacementMode( Qgis::AnnotationPlacementMode::FixedSize ); + + item->setOffsetFromCallout( QSizeF( text->frameOffsetFromReferencePointMm().x() + margins.left(), + text->frameOffsetFromReferencePointMm().y() + margins.top() ) ); + item->setOffsetFromCalloutUnit( Qgis::RenderUnit::Millimeters ); + item->setBackgroundEnabled( false ); + item->setFrameEnabled( false ); + } + else + { + item->setPlacementMode( Qgis::AnnotationPlacementMode::RelativeToMapFrame ); + item->setBounds( QgsRectangle( text->relativePosition().x(), text->relativePosition().y(), + text->relativePosition().x(), text->relativePosition().y() ) ); + if ( QgsFillSymbol *fill = text->fillSymbol() ) + { + item->setBackgroundEnabled( true ); + item->setBackgroundSymbol( fill->clone() ); + } + } + + return item; + } return nullptr; } diff --git a/src/core/annotations/qgsannotationrectangletextitem.cpp b/src/core/annotations/qgsannotationrectangletextitem.cpp index be0883ec95ba..fbb02c46d30b 100644 --- a/src/core/annotations/qgsannotationrectangletextitem.cpp +++ b/src/core/annotations/qgsannotationrectangletextitem.cpp @@ -28,6 +28,8 @@ #include "qgsunittypes.h" #include "qgsapplication.h" #include "qgscalloutsregistry.h" +#include "qgspolygon.h" +#include "qgslinestring.h" QgsAnnotationRectangleTextItem::QgsAnnotationRectangleTextItem( const QString &text, const QgsRectangle &bounds ) : QgsAnnotationItem() @@ -50,7 +52,7 @@ QString QgsAnnotationRectangleTextItem::type() const void QgsAnnotationRectangleTextItem::render( QgsRenderContext &context, QgsFeedback *feedback ) { QgsRectangle bounds = mBounds; - if ( context.coordinateTransform().isValid() ) + if ( mPlacementMode != Qgis::AnnotationPlacementMode::RelativeToMapFrame && context.coordinateTransform().isValid() ) { try { @@ -62,7 +64,68 @@ void QgsAnnotationRectangleTextItem::render( QgsRenderContext &context, QgsFeedb } } - const QRectF painterBounds = context.mapToPixel().transformBounds( bounds.toRectF() ); + QRectF painterBounds; + switch ( mPlacementMode ) + { + case Qgis::AnnotationPlacementMode::SpatialBounds: + painterBounds = context.mapToPixel().transformBounds( bounds.toRectF() ); + break; + + case Qgis::AnnotationPlacementMode::FixedSize: + { + const double widthPixels = context.convertToPainterUnits( mFixedSize.width(), mFixedSizeUnit ); + const double heightPixels = context.convertToPainterUnits( mFixedSize.height(), mFixedSizeUnit ); + + 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; + } + + case Qgis::AnnotationPlacementMode::RelativeToMapFrame: + { + const double widthPixels = context.convertToPainterUnits( mFixedSize.width(), mFixedSizeUnit ); + const double heightPixels = context.convertToPainterUnits( mFixedSize.height(), mFixedSizeUnit ); + + QPointF center = bounds.center().toQPointF(); + center.rx() *= context.outputSize().width(); + center.ry() *= context.outputSize().height(); + + painterBounds = QRectF( center.x() - widthPixels * 0.5, + center.y() - heightPixels * 0.5, + widthPixels, heightPixels ); + break; + } + } + if ( painterBounds.width() < 1 || painterBounds.height() < 1 ) return; @@ -73,7 +136,7 @@ void QgsAnnotationRectangleTextItem::render( QgsRenderContext &context, QgsFeedb mBackgroundSymbol->stopRender( context ); } - if ( callout() ) + if ( mPlacementMode != Qgis::AnnotationPlacementMode::RelativeToMapFrame && callout() ) { QgsCallout::QgsCalloutContext calloutContext; renderCallout( context, painterBounds, 0, calloutContext, feedback ); @@ -126,6 +189,11 @@ bool QgsAnnotationRectangleTextItem::writeXml( QDomElement &element, QDomDocumen element.setAttribute( QStringLiteral( "yMin" ), qgsDoubleToString( mBounds.yMinimum() ) ); element.setAttribute( QStringLiteral( "yMax" ), qgsDoubleToString( mBounds.yMaximum() ) ); + element.setAttribute( QStringLiteral( "sizeMode" ), qgsEnumValueToKey( mPlacementMode ) ); + element.setAttribute( QStringLiteral( "fixedWidth" ), qgsDoubleToString( mFixedSize.width() ) ); + element.setAttribute( QStringLiteral( "fixedHeight" ), qgsDoubleToString( mFixedSize.height() ) ); + element.setAttribute( QStringLiteral( "fixedSizeUnit" ), QgsUnitTypes::encodeUnit( mFixedSizeUnit ) ); + element.setAttribute( QStringLiteral( "backgroundEnabled" ), mDrawBackground ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ); if ( mBackgroundSymbol ) { @@ -146,31 +214,69 @@ bool QgsAnnotationRectangleTextItem::writeXml( QDomElement &element, QDomDocumen return true; } -QList QgsAnnotationRectangleTextItem::nodesV2( const QgsAnnotationItemEditContext & ) const +QList QgsAnnotationRectangleTextItem::nodesV2( const QgsAnnotationItemEditContext &context ) const { - QList res = - { - QgsAnnotationItemNode( QgsVertexId( 0, 0, 0 ), QgsPointXY( mBounds.xMinimum(), mBounds.yMinimum() ), Qgis::AnnotationItemNodeType::VertexHandle ), - QgsAnnotationItemNode( QgsVertexId( 0, 0, 1 ), QgsPointXY( mBounds.xMaximum(), mBounds.yMinimum() ), Qgis::AnnotationItemNodeType::VertexHandle ), - QgsAnnotationItemNode( QgsVertexId( 0, 0, 2 ), QgsPointXY( mBounds.xMaximum(), mBounds.yMaximum() ), Qgis::AnnotationItemNodeType::VertexHandle ), - QgsAnnotationItemNode( QgsVertexId( 0, 0, 3 ), QgsPointXY( mBounds.xMinimum(), mBounds.yMaximum() ), Qgis::AnnotationItemNodeType::VertexHandle ), - }; - - QgsPointXY calloutNodePoint; - if ( !calloutAnchor().isEmpty() ) + QList res; + switch ( mPlacementMode ) { - calloutNodePoint = calloutAnchor().asPoint(); - } - else - { - calloutNodePoint = mBounds.center(); - } - res.append( QgsAnnotationItemNode( QgsVertexId( 1, 0, 0 ), calloutNodePoint, Qgis::AnnotationItemNodeType::CalloutHandle ) ); + case Qgis::AnnotationPlacementMode::SpatialBounds: + { + res = + { + QgsAnnotationItemNode( QgsVertexId( 0, 0, 0 ), QgsPointXY( mBounds.xMinimum(), mBounds.yMinimum() ), Qgis::AnnotationItemNodeType::VertexHandle ), + QgsAnnotationItemNode( QgsVertexId( 0, 0, 1 ), QgsPointXY( mBounds.xMaximum(), mBounds.yMinimum() ), Qgis::AnnotationItemNodeType::VertexHandle ), + QgsAnnotationItemNode( QgsVertexId( 0, 0, 2 ), QgsPointXY( mBounds.xMaximum(), mBounds.yMaximum() ), Qgis::AnnotationItemNodeType::VertexHandle ), + QgsAnnotationItemNode( QgsVertexId( 0, 0, 3 ), QgsPointXY( mBounds.xMinimum(), mBounds.yMaximum() ), Qgis::AnnotationItemNodeType::VertexHandle ), + }; + + QgsPointXY calloutNodePoint; + if ( !calloutAnchor().isEmpty() ) + { + calloutNodePoint = calloutAnchor().asPoint(); + } + else + { + calloutNodePoint = mBounds.center(); + } + res.append( QgsAnnotationItemNode( QgsVertexId( 1, 0, 0 ), calloutNodePoint, Qgis::AnnotationItemNodeType::CalloutHandle ) ); + + return res; + } + + case Qgis::AnnotationPlacementMode::FixedSize: + { + res = + { + QgsAnnotationItemNode( QgsVertexId( 0, 0, 0 ), mBounds.center(), Qgis::AnnotationItemNodeType::VertexHandle ) + }; - return res; + QgsPointXY calloutNodePoint; + if ( !calloutAnchor().isEmpty() ) + { + calloutNodePoint = calloutAnchor().asPoint(); + } + else + { + calloutNodePoint = QgsPointXY( context.currentItemBounds().xMinimum(), context.currentItemBounds().yMinimum() ); + } + res.append( QgsAnnotationItemNode( QgsVertexId( 1, 0, 0 ), calloutNodePoint, Qgis::AnnotationItemNodeType::CalloutHandle ) ); + + return res; + } + + case Qgis::AnnotationPlacementMode::RelativeToMapFrame: + { + return + { + QgsAnnotationItemNode( QgsVertexId( 0, 0, 0 ), + context.currentItemBounds().center(), Qgis::AnnotationItemNodeType::VertexHandle ) + }; + } + } + BUILTIN_UNREACHABLE } -Qgis::AnnotationItemEditOperationResult QgsAnnotationRectangleTextItem::applyEditV2( QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext & ) +Qgis::AnnotationItemEditOperationResult QgsAnnotationRectangleTextItem::applyEditV2( QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext &context ) { switch ( operation->type() ) { @@ -179,55 +285,112 @@ Qgis::AnnotationItemEditOperationResult QgsAnnotationRectangleTextItem::applyEdi QgsAnnotationItemEditOperationMoveNode *moveOperation = dynamic_cast< QgsAnnotationItemEditOperationMoveNode * >( operation ); if ( moveOperation->nodeId().part == 0 ) { - switch ( moveOperation->nodeId().vertex ) + switch ( mPlacementMode ) { - case 0: - mBounds = QgsRectangle( moveOperation->after().x(), - moveOperation->after().y(), - mBounds.xMaximum(), - mBounds.yMaximum() ); - break; - case 1: - mBounds = QgsRectangle( mBounds.xMinimum(), - moveOperation->after().y(), - moveOperation->after().x(), - mBounds.yMaximum() ); - break; - case 2: - mBounds = QgsRectangle( mBounds.xMinimum(), - mBounds.yMinimum(), - moveOperation->after().x(), - moveOperation->after().y() ); - break; - case 3: - mBounds = QgsRectangle( moveOperation->after().x(), - mBounds.yMinimum(), - mBounds.xMaximum(), - moveOperation->after().y() ); - break; - default: - break; + case Qgis::AnnotationPlacementMode::SpatialBounds: + { + switch ( moveOperation->nodeId().vertex ) + { + case 0: + mBounds = QgsRectangle( moveOperation->after().x(), + moveOperation->after().y(), + mBounds.xMaximum(), + mBounds.yMaximum() ); + break; + case 1: + mBounds = QgsRectangle( mBounds.xMinimum(), + moveOperation->after().y(), + moveOperation->after().x(), + mBounds.yMaximum() ); + break; + case 2: + mBounds = QgsRectangle( mBounds.xMinimum(), + mBounds.yMinimum(), + moveOperation->after().x(), + moveOperation->after().y() ); + break; + case 3: + mBounds = QgsRectangle( moveOperation->after().x(), + mBounds.yMinimum(), + mBounds.xMaximum(), + moveOperation->after().y() ); + break; + default: + break; + } + return Qgis::AnnotationItemEditOperationResult::Success; + } + + case Qgis::AnnotationPlacementMode::FixedSize: + { + mBounds = QgsRectangle::fromCenterAndSize( moveOperation->after(), + mBounds.width(), + mBounds.height() ); + return Qgis::AnnotationItemEditOperationResult::Success; + } + + case Qgis::AnnotationPlacementMode::RelativeToMapFrame: + { + const double deltaX = moveOperation->translationXPixels() / context.renderContext().outputSize().width(); + const double deltaY = moveOperation->translationYPixels() / context.renderContext().outputSize().height(); + mBounds = QgsRectangle::fromCenterAndSize( QgsPointXY( mBounds.center().x() + deltaX, mBounds.center().y() + deltaY ), + mBounds.width(), mBounds.height() ); + return Qgis::AnnotationItemEditOperationResult::Success; + } } } - else + else if ( moveOperation->nodeId().part == 1 ) { setCalloutAnchor( QgsGeometry::fromPoint( moveOperation->after() ) ); if ( !callout() ) { setCallout( QgsApplication::calloutRegistry()->defaultCallout() ); } + return Qgis::AnnotationItemEditOperationResult::Success; } - - return Qgis::AnnotationItemEditOperationResult::Success; + break; } 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 ( mPlacementMode ) + { + + case Qgis::AnnotationPlacementMode::SpatialBounds: + mBounds = QgsRectangle( mBounds.xMinimum() + moveOperation->translationX(), + mBounds.yMinimum() + moveOperation->translationY(), + mBounds.xMaximum() + moveOperation->translationX(), + mBounds.yMaximum() + moveOperation->translationY() ); + break; + + case Qgis::AnnotationPlacementMode::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; + } + + case Qgis::AnnotationPlacementMode::RelativeToMapFrame: + { + const double deltaX = moveOperation->translationXPixels() / context.renderContext().outputSize().width(); + const double deltaY = moveOperation->translationYPixels() / context.renderContext().outputSize().height(); + mBounds = QgsRectangle::fromCenterAndSize( QgsPointXY( mBounds.center().x() + deltaX, mBounds.center().y() + deltaY ), + mBounds.width(), mBounds.height() ); + break; + } + } return Qgis::AnnotationItemEditOperationResult::Success; } @@ -238,7 +401,7 @@ Qgis::AnnotationItemEditOperationResult QgsAnnotationRectangleTextItem::applyEdi return Qgis::AnnotationItemEditOperationResult::Invalid; } -QgsAnnotationItemEditOperationTransientResults *QgsAnnotationRectangleTextItem::transientEditResultsV2( QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext & ) +QgsAnnotationItemEditOperationTransientResults *QgsAnnotationRectangleTextItem::transientEditResultsV2( QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext &context ) { switch ( operation->type() ) { @@ -247,46 +410,130 @@ QgsAnnotationItemEditOperationTransientResults *QgsAnnotationRectangleTextItem:: QgsAnnotationItemEditOperationMoveNode *moveOperation = dynamic_cast< QgsAnnotationItemEditOperationMoveNode * >( operation ); if ( moveOperation->nodeId().part == 0 ) { - QgsRectangle modifiedBounds = mBounds; - switch ( moveOperation->nodeId().vertex ) + switch ( mPlacementMode ) { - case 0: - modifiedBounds.setXMinimum( moveOperation->after().x() ); - modifiedBounds.setYMinimum( moveOperation->after().y() ); - break; - case 1: - modifiedBounds.setXMaximum( moveOperation->after().x() ); - modifiedBounds.setYMinimum( moveOperation->after().y() ); - break; - case 2: - modifiedBounds.setXMaximum( moveOperation->after().x() ); - modifiedBounds.setYMaximum( moveOperation->after().y() ); - break; - case 3: - modifiedBounds.setXMinimum( moveOperation->after().x() ); - modifiedBounds.setYMaximum( moveOperation->after().y() ); - break; - default: - break; + case Qgis::AnnotationPlacementMode::SpatialBounds: + { + QgsRectangle modifiedBounds = mBounds; + switch ( moveOperation->nodeId().vertex ) + { + case 0: + modifiedBounds.setXMinimum( moveOperation->after().x() ); + modifiedBounds.setYMinimum( moveOperation->after().y() ); + break; + case 1: + modifiedBounds.setXMaximum( moveOperation->after().x() ); + modifiedBounds.setYMinimum( moveOperation->after().y() ); + break; + case 2: + modifiedBounds.setXMaximum( moveOperation->after().x() ); + modifiedBounds.setYMaximum( moveOperation->after().y() ); + break; + case 3: + modifiedBounds.setXMinimum( moveOperation->after().x() ); + modifiedBounds.setYMaximum( moveOperation->after().y() ); + break; + default: + break; + } + return new QgsAnnotationItemEditOperationTransientResults( QgsGeometry::fromRect( modifiedBounds ) ); + } + case Qgis::AnnotationPlacementMode::FixedSize: + { + const QgsRectangle currentBounds = context.currentItemBounds(); + const QgsRectangle newBounds = QgsRectangle::fromCenterAndSize( moveOperation->after(), currentBounds.width(), currentBounds.height() ); + return new QgsAnnotationItemEditOperationTransientResults( QgsGeometry::fromRect( newBounds ) ); + } + case Qgis::AnnotationPlacementMode::RelativeToMapFrame: + { + const QgsRectangle currentBounds = context.currentItemBounds(); + const QgsRectangle newBounds = QgsRectangle::fromCenterAndSize( currentBounds.center() + ( moveOperation->after() - moveOperation->before() ), + currentBounds.width(), currentBounds.height() ); + return new QgsAnnotationItemEditOperationTransientResults( QgsGeometry::fromRect( newBounds ) ); + } } - - return new QgsAnnotationItemEditOperationTransientResults( QgsGeometry::fromRect( modifiedBounds ) ); } else { QgsAnnotationItemEditOperationMoveNode *moveOperation = dynamic_cast< QgsAnnotationItemEditOperationMoveNode * >( operation ); return new QgsAnnotationItemEditOperationTransientResults( QgsGeometry( moveOperation->after().clone() ) ); } + break; } case QgsAbstractAnnotationItemEditOperation::Type::TranslateItem: { QgsAnnotationItemEditOperationTranslateItem *moveOperation = qgis::down_cast< QgsAnnotationItemEditOperationTranslateItem * >( operation ); - const QgsRectangle modifiedBounds( mBounds.xMinimum() + moveOperation->translationX(), - mBounds.yMinimum() + moveOperation->translationY(), - mBounds.xMaximum() + moveOperation->translationX(), - mBounds.yMaximum() + moveOperation->translationY() ); - return new QgsAnnotationItemEditOperationTransientResults( QgsGeometry::fromRect( modifiedBounds ) ); + switch ( mPlacementMode ) + { + case Qgis::AnnotationPlacementMode::SpatialBounds: + { + const QgsRectangle modifiedBounds( mBounds.xMinimum() + moveOperation->translationX(), + mBounds.yMinimum() + moveOperation->translationY(), + mBounds.xMaximum() + moveOperation->translationX(), + mBounds.yMaximum() + moveOperation->translationY() ); + return new QgsAnnotationItemEditOperationTransientResults( QgsGeometry::fromRect( modifiedBounds ) ); + } + + case Qgis::AnnotationPlacementMode::FixedSize: + { + 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( 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 ) ); + } + } + + case Qgis::AnnotationPlacementMode::RelativeToMapFrame: + { + const QgsRectangle currentBounds = context.currentItemBounds(); + const QgsRectangle newBounds = QgsRectangle::fromCenterAndSize( currentBounds.center() + QgsVector( moveOperation->translationX(), moveOperation->translationY() ), + currentBounds.width(), currentBounds.height() ); + return new QgsAnnotationItemEditOperationTransientResults( QgsGeometry::fromRect( newBounds ) ); + } + } + break; } case QgsAbstractAnnotationItemEditOperation::Type::DeleteNode: @@ -317,6 +564,12 @@ bool QgsAnnotationRectangleTextItem::readXml( const QDomElement &element, const mBounds.setXMaximum( element.attribute( QStringLiteral( "xMax" ) ).toDouble() ); mBounds.setYMinimum( element.attribute( QStringLiteral( "yMin" ) ).toDouble() ); mBounds.setYMaximum( element.attribute( QStringLiteral( "yMax" ) ).toDouble() ); + mPlacementMode = qgsEnumKeyToValue( element.attribute( QStringLiteral( "sizeMode" ) ), Qgis::AnnotationPlacementMode::SpatialBounds ); + mFixedSize = QSizeF( + element.attribute( QStringLiteral( "fixedWidth" ) ).toDouble(), + element.attribute( QStringLiteral( "fixedHeight" ) ).toDouble() + ); + mFixedSizeUnit = QgsUnitTypes::decodeRenderUnit( element.attribute( QStringLiteral( "fixedSizeUnit" ) ) ); mMargins = QgsMargins::fromString( element.attribute( QStringLiteral( "margins" ) ) ); mMarginUnit = QgsUnitTypes::decodeRenderUnit( element.attribute( QStringLiteral( "marginUnit" ), QgsUnitTypes::encodeUnit( Qgis::RenderUnit::Millimeters ) ) ); @@ -347,6 +600,9 @@ QgsAnnotationRectangleTextItem *QgsAnnotationRectangleTextItem::clone() const item->setFormat( mTextFormat ); item->setAlignment( mAlignment ); + item->setPlacementMode( mPlacementMode ); + item->setFixedSize( mFixedSize ); + item->setFixedSizeUnit( mFixedSizeUnit ); item->setBackgroundEnabled( mDrawBackground ); if ( mBackgroundSymbol ) @@ -365,20 +621,179 @@ QgsAnnotationRectangleTextItem *QgsAnnotationRectangleTextItem::clone() const QgsRectangle QgsAnnotationRectangleTextItem::boundingBox() const { - QgsRectangle bounds = mBounds; - if ( callout() && !calloutAnchor().isEmpty() ) + QgsRectangle bounds; + switch ( mPlacementMode ) { - QgsGeometry anchor = calloutAnchor(); - bounds.combineExtentWith( anchor.boundingBox() ); + case Qgis::AnnotationPlacementMode::SpatialBounds: + { + bounds = mBounds; + if ( callout() && !calloutAnchor().isEmpty() ) + { + QgsGeometry anchor = calloutAnchor(); + bounds.combineExtentWith( anchor.boundingBox() ); + } + break; + } + + case Qgis::AnnotationPlacementMode::FixedSize: + if ( callout() && !calloutAnchor().isEmpty() ) + { + bounds = calloutAnchor().boundingBox(); + } + else + { + bounds = QgsRectangle( mBounds.center(), mBounds.center() ); + } + break; + + case Qgis::AnnotationPlacementMode::RelativeToMapFrame: + bounds = mBounds; + break; } + return bounds; } +QgsRectangle QgsAnnotationRectangleTextItem::boundingBox( QgsRenderContext &context ) const +{ + switch ( mPlacementMode ) + { + case Qgis::AnnotationPlacementMode::SpatialBounds: + return QgsAnnotationRectangleTextItem::boundingBox(); + + case Qgis::AnnotationPlacementMode::FixedSize: + { + const double widthPixels = context.convertToPainterUnits( mFixedSize.width(), mFixedSizeUnit ); + const double heightPixels = context.convertToPainterUnits( mFixedSize.height(), mFixedSizeUnit ); + + 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() ); + const QgsPointXY bottomRight = context.mapToPixel().toMapCoordinates( boundsInPixels.right(), boundsInPixels.bottom() ); + + const QgsRectangle boundsMapUnits = QgsRectangle( topLeft.x(), bottomLeft.y(), bottomRight.x(), topRight.y() ); + QgsRectangle textRect = context.coordinateTransform().transformBoundingBox( boundsMapUnits, Qgis::TransformDirection::Reverse ); + return textRect; + } + + case Qgis::AnnotationPlacementMode::RelativeToMapFrame: + { + const double widthPixels = context.convertToPainterUnits( mFixedSize.width(), mFixedSizeUnit ); + const double heightPixels = context.convertToPainterUnits( mFixedSize.height(), mFixedSizeUnit ); + + QRectF boundsInPixels; + + const double centerMapX = context.mapExtent().xMinimum() + mBounds.center().x() * context.mapExtent().width(); + const double centerMapY = context.mapExtent().yMaximum() - mBounds.center().y() * context.mapExtent().height(); + QPointF center( centerMapX, centerMapY ); + if ( context.coordinateTransform().isValid() ) + { + double x = centerMapX; + double y = centerMapY; + 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() ); + const QgsPointXY bottomRight = context.mapToPixel().toMapCoordinates( boundsInPixels.right(), boundsInPixels.bottom() ); + + const QgsRectangle boundsMapUnits = QgsRectangle( topLeft.x(), bottomLeft.y(), bottomRight.x(), topRight.y() ); + QgsRectangle textRect = context.coordinateTransform().transformBoundingBox( boundsMapUnits, Qgis::TransformDirection::Reverse ); + return textRect; + } + } + BUILTIN_UNREACHABLE +} + void QgsAnnotationRectangleTextItem::setBounds( const QgsRectangle &bounds ) { mBounds = bounds; } +Qgis::AnnotationPlacementMode QgsAnnotationRectangleTextItem::placementMode() const +{ + return mPlacementMode; +} + +void QgsAnnotationRectangleTextItem::setPlacementMode( Qgis::AnnotationPlacementMode mode ) +{ + mPlacementMode = mode; +} + +QSizeF QgsAnnotationRectangleTextItem::fixedSize() const +{ + return mFixedSize; +} + +void QgsAnnotationRectangleTextItem::setFixedSize( const QSizeF &size ) +{ + mFixedSize = size; +} + +Qgis::RenderUnit QgsAnnotationRectangleTextItem::fixedSizeUnit() const +{ + return mFixedSizeUnit; +} + +void QgsAnnotationRectangleTextItem::setFixedSizeUnit( Qgis::RenderUnit unit ) +{ + mFixedSizeUnit = unit; +} + const QgsFillSymbol *QgsAnnotationRectangleTextItem::backgroundSymbol() const { return mBackgroundSymbol.get(); @@ -401,8 +816,18 @@ void QgsAnnotationRectangleTextItem::setFrameSymbol( QgsFillSymbol *symbol ) Qgis::AnnotationItemFlags QgsAnnotationRectangleTextItem::flags() const { - return Qgis::AnnotationItemFlag::SupportsReferenceScale - | Qgis::AnnotationItemFlag::SupportsCallouts; + switch ( mPlacementMode ) + { + case Qgis::AnnotationPlacementMode::SpatialBounds: + return Qgis::AnnotationItemFlag::SupportsReferenceScale + | Qgis::AnnotationItemFlag::SupportsCallouts; + case Qgis::AnnotationPlacementMode::FixedSize: + return Qgis::AnnotationItemFlag::ScaleDependentBoundingBox + | Qgis::AnnotationItemFlag::SupportsCallouts; + case Qgis::AnnotationPlacementMode::RelativeToMapFrame: + return Qgis::AnnotationItemFlag::ScaleDependentBoundingBox; + } + BUILTIN_UNREACHABLE } QgsTextFormat QgsAnnotationRectangleTextItem::format() const diff --git a/src/core/annotations/qgsannotationrectangletextitem.h b/src/core/annotations/qgsannotationrectangletextitem.h index 739157bfccd4..ceffe780f0f3 100644 --- a/src/core/annotations/qgsannotationrectangletextitem.h +++ b/src/core/annotations/qgsannotationrectangletextitem.h @@ -57,6 +57,7 @@ class CORE_EXPORT QgsAnnotationRectangleTextItem : public QgsAnnotationItem bool readXml( const QDomElement &element, const QgsReadWriteContext &context ) override; QgsAnnotationRectangleTextItem *clone() const override SIP_FACTORY; QgsRectangle boundingBox() const override; + QgsRectangle boundingBox( QgsRenderContext &context ) const override; /** * Returns the bounds of the text. @@ -90,6 +91,56 @@ class CORE_EXPORT QgsAnnotationRectangleTextItem : public QgsAnnotationItem */ void setText( const QString &text ) { mText = text; } + /** + * Returns the placement mode for the text. + * + * \see setPlacementMode() + */ + Qgis::AnnotationPlacementMode placementMode() const; + + /** + * Sets the placement \a mode for the text. + * + * \see placementMode() + */ + void setPlacementMode( Qgis::AnnotationPlacementMode mode ); + + /** + * Returns the fixed size to use for the text, when the placementMode() is Qgis::AnnotationPlacementMode::FixedSize. + * + * Units are retrieved via fixedSizeUnit() + * + * \see setFixedSize() + * \see fixedSizeUnit() + */ + QSizeF fixedSize() const; + + /** + * Sets the fixed \a size to use for the text, when the placementMode() is Qgis::AnnotationPlacementMode::FixedSize. + * + * Units are set via setFixedSizeUnit() + * + * \see fixedSize() + * \see setFixedSizeUnit() + */ + void setFixedSize( const QSizeF &size ); + + /** + * Returns the units to use for fixed text sizes, when the placementMode() is Qgis::AnnotationPlacementMode::FixedSize. + * + * \see setFixedSizeUnit() + * \see fixedSize() + */ + Qgis::RenderUnit fixedSizeUnit() const; + + /** + * Sets the \a unit to use for fixed text sizes, when the placementMode() is Qgis::AnnotationPlacementMode::FixedSize. + * + * \see fixedSizeUnit() + * \see setFixedSize() + */ + void setFixedSizeUnit( Qgis::RenderUnit unit ); + /** * Returns the text format used to render the text. * @@ -224,7 +275,11 @@ class CORE_EXPORT QgsAnnotationRectangleTextItem : public QgsAnnotationItem private: + Qgis::AnnotationPlacementMode mPlacementMode = Qgis::AnnotationPlacementMode::SpatialBounds; QgsRectangle mBounds; + QSizeF mFixedSize; + Qgis::RenderUnit mFixedSizeUnit = Qgis::RenderUnit::Millimeters; + QString mText; QgsTextFormat mTextFormat; Qt::Alignment mAlignment = Qt::AlignLeft; From ebd2561939219fbf8fa7fb1af22ac9e77b61c3f0 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 19 Aug 2024 14:57:23 +1000 Subject: [PATCH 07/15] Remove action for creating old text annotations The new Text in Rect annotation item should be used instead --- src/app/CMakeLists.txt | 1 - src/app/maptools/qgsappmaptools.cpp | 2 -- src/app/maptools/qgsappmaptools.h | 1 - src/app/qgisapp.cpp | 20 +++------------- src/app/qgisapp.h | 1 - src/app/qgsmaptooltextannotation.cpp | 32 ------------------------- src/app/qgsmaptooltextannotation.h | 35 ---------------------------- src/ui/qgisapp.ui | 13 ----------- 8 files changed, 3 insertions(+), 102 deletions(-) delete mode 100644 src/app/qgsmaptooltextannotation.cpp delete mode 100644 src/app/qgsmaptooltextannotation.h diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index cb10cc3a4768..1419906c91a7 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -104,7 +104,6 @@ set(QGIS_APP_SRCS qgsmaptoolsimplify.cpp qgsmaptoolsplitfeatures.cpp qgsmaptoolsplitparts.cpp - qgsmaptooltextannotation.cpp annotations/qgsannotationitempropertieswidget.cpp annotations/qgsannotationlayerproperties.cpp diff --git a/src/app/maptools/qgsappmaptools.cpp b/src/app/maptools/qgsappmaptools.cpp index 1831072e88ef..c726e7c65088 100644 --- a/src/app/maptools/qgsappmaptools.cpp +++ b/src/app/maptools/qgsappmaptools.cpp @@ -23,7 +23,6 @@ #include "qgsmaptoolpan.h" #include "qgsmaptoolfeatureaction.h" #include "qgsmeasuretool.h" -#include "qgsmaptooltextannotation.h" #include "qgsmaptoolhtmlannotation.h" #include "qgsmaptoolmeasureangle.h" #include "qgsmaptoolmeasurebearing.h" @@ -70,7 +69,6 @@ QgsAppMapTools::QgsAppMapTools( QgsMapCanvas *canvas, QgsAdvancedDigitizingDockW mTools.insert( Tool::MeasureArea, new QgsMeasureTool( canvas, true /* area */ ) ); mTools.insert( Tool::MeasureAngle, new QgsMapToolMeasureAngle( canvas ) ); mTools.insert( Tool::MeasureBearing, new QgsMapToolMeasureBearing( canvas ) ); - mTools.insert( Tool::TextAnnotation, new QgsMapToolTextAnnotation( canvas ) ); mTools.insert( Tool::FormAnnotation, new QgsMapToolFormAnnotation( canvas ) ); mTools.insert( Tool::HtmlAnnotation, new QgsMapToolHtmlAnnotation( canvas ) ); mTools.insert( Tool::AddFeature, new QgsMapToolAddFeature( canvas, cadDock, QgsMapToolCapture::CaptureNone ) ); diff --git a/src/app/maptools/qgsappmaptools.h b/src/app/maptools/qgsappmaptools.h index cb329d476c46..8d7bad78d0f6 100644 --- a/src/app/maptools/qgsappmaptools.h +++ b/src/app/maptools/qgsappmaptools.h @@ -68,7 +68,6 @@ class QgsAppMapTools Annotation, // Unused FormAnnotation, HtmlAnnotation, - TextAnnotation, PinLabels, ShowHideLabels, MoveLabel, diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 9c880287c93d..952c536af77d 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -3021,7 +3021,6 @@ void QgisApp::createActions() connect( mActionMapTips, &QAction::toggled, this, &QgisApp::toggleMapTips ); connect( mActionNewBookmark, &QAction::triggered, this, [ = ] { newBookmark( true ); } ); connect( mActionDraw, &QAction::triggered, this, [this] { refreshMapCanvas( true ); } ); - connect( mActionTextAnnotation, &QAction::triggered, this, &QgisApp::addTextAnnotation ); connect( mActionFormAnnotation, &QAction::triggered, this, &QgisApp::addFormAnnotation ); connect( mActionHtmlAnnotation, &QAction::triggered, this, &QgisApp::addHtmlAnnotation ); connect( mActionLabeling, &QAction::triggered, this, &QgisApp::labeling ); @@ -3966,22 +3965,18 @@ void QgisApp::createToolBars() bt = new QToolButton(); bt->setPopupMode( QToolButton::MenuButtonPopup ); - bt->addAction( mActionTextAnnotation ); bt->addAction( mActionFormAnnotation ); bt->addAction( mActionHtmlAnnotation ); - QAction *defAnnotationAction = mActionTextAnnotation; + QAction *defAnnotationAction = mActionHtmlAnnotation; switch ( settings.value( QStringLiteral( "UI/annotationTool" ), 0 ).toInt() ) { case 0: - defAnnotationAction = mActionTextAnnotation; + defAnnotationAction = mActionHtmlAnnotation; break; case 1: defAnnotationAction = mActionFormAnnotation; break; - case 2: - defAnnotationAction = mActionHtmlAnnotation; - break; } bt->setDefaultAction( defAnnotationAction ); QAction *annotationAction = mAnnotationsToolBar->addWidget( bt ); @@ -4359,7 +4354,6 @@ void QgisApp::setTheme( const QString &themeName ) mActionAddToOverview->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionInOverview.svg" ) ) ); mActionFormAnnotation->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionFormAnnotation.svg" ) ) ); mActionHtmlAnnotation->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionHtmlAnnotation.svg" ) ) ); - mActionTextAnnotation->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionTextAnnotation.svg" ) ) ); mActionLabeling->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionLabeling.svg" ) ) ); mActionShowPinnedLabels->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionShowPinnedLabels.svg" ) ) ); mActionPinLabels->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionPinLabels.svg" ) ) ); @@ -4579,7 +4573,6 @@ void QgisApp::setupCanvasTools() mMapTools->mapTool( QgsAppMapTools::MeasureArea )->setAction( mActionMeasureArea ); mMapTools->mapTool( QgsAppMapTools::MeasureAngle )->setAction( mActionMeasureAngle ); mMapTools->mapTool( QgsAppMapTools::MeasureBearing )->setAction( mActionMeasureBearing ); - mMapTools->mapTool( QgsAppMapTools::TextAnnotation )->setAction( mActionTextAnnotation ); mMapTools->mapTool( QgsAppMapTools::FormAnnotation )->setAction( mActionFormAnnotation ); mMapTools->mapTool( QgsAppMapTools::HtmlAnnotation )->setAction( mActionHtmlAnnotation ); mMapTools->mapTool( QgsAppMapTools::AddFeature )->setAction( mActionAddFeature ); @@ -7933,11 +7926,6 @@ void QgisApp::addHtmlAnnotation() mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::HtmlAnnotation ) ); } -void QgisApp::addTextAnnotation() -{ - mMapCanvas->setMapTool( mMapTools->mapTool( QgsAppMapTools::TextAnnotation ) ); -} - void QgisApp::reprojectAnnotations() { const auto annotations = annotationItems(); @@ -17015,12 +17003,10 @@ void QgisApp::toolButtonActionTriggered( QAction *action ) settings.setValue( QStringLiteral( "UI/measureTool" ), 1 ); else if ( action == mActionMeasureAngle ) settings.setValue( QStringLiteral( "UI/measureTool" ), 2 ); - else if ( action == mActionTextAnnotation ) - settings.setValue( QStringLiteral( "UI/annotationTool" ), 0 ); else if ( action == mActionFormAnnotation ) settings.setValue( QStringLiteral( "UI/annotationTool" ), 1 ); else if ( action == mActionHtmlAnnotation ) - settings.setValue( QStringLiteral( "UI/annotationTool" ), 2 ); + settings.setValue( QStringLiteral( "UI/annotationTool" ), 0 ); else if ( action == mActionNewSpatiaLiteLayer ) settings.setValue( QStringLiteral( "UI/defaultNewLayer" ), 0 ); else if ( action == mActionNewVectorLayer ) diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h index 023a1356e2a1..e35865276bb8 100644 --- a/src/app/qgisapp.h +++ b/src/app/qgisapp.h @@ -1947,7 +1947,6 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow //annotations void addFormAnnotation(); - void addTextAnnotation(); void addHtmlAnnotation(); void reprojectAnnotations(); diff --git a/src/app/qgsmaptooltextannotation.cpp b/src/app/qgsmaptooltextannotation.cpp deleted file mode 100644 index b52274b078d6..000000000000 --- a/src/app/qgsmaptooltextannotation.cpp +++ /dev/null @@ -1,32 +0,0 @@ -/*************************************************************************** - qgsmaptooltextannotation.cpp - ------------------------------- - begin : February 9, 2010 - copyright : (C) 2010 by Marco Hugentobler - email : marco dot hugentobler at hugis dot net - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#include "qgsmaptooltextannotation.h" -#include "qgstextannotation.h" -#include "qgsproject.h" -#include - -QgsMapToolTextAnnotation::QgsMapToolTextAnnotation( QgsMapCanvas *canvas ): QgsMapToolAnnotation( canvas ) -{ - -} - -QgsAnnotation *QgsMapToolTextAnnotation::createItem() const -{ - return new QgsTextAnnotation(); -} - diff --git a/src/app/qgsmaptooltextannotation.h b/src/app/qgsmaptooltextannotation.h deleted file mode 100644 index 55762fd15b56..000000000000 --- a/src/app/qgsmaptooltextannotation.h +++ /dev/null @@ -1,35 +0,0 @@ -/*************************************************************************** - qgsmaptooltextannotation.h - ------------------------- - begin : February 9, 2010 - copyright : (C) 2010 by Marco Hugentobler - email : marco dot hugentobler at hugis dot net - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#ifndef QGSMAPTOOLTEXTANNOTATION_H -#define QGSMAPTOOLTEXTANNOTATION_H - -#include "qgsmaptoolannotation.h" -#include "qgis_app.h" - -class APP_EXPORT QgsMapToolTextAnnotation: public QgsMapToolAnnotation -{ - Q_OBJECT - - public: - QgsMapToolTextAnnotation( QgsMapCanvas *canvas ); - - protected: - QgsAnnotation *createItem() const override; -}; - -#endif // QGSMAPTOOLTEXTANNOTATION_H diff --git a/src/ui/qgisapp.ui b/src/ui/qgisapp.ui index 9a48bd4b20b4..57cbb783fde7 100644 --- a/src/ui/qgisapp.ui +++ b/src/ui/qgisapp.ui @@ -349,7 +349,6 @@ Add Annotation - @@ -1506,18 +1505,6 @@ Shift+O to turn segments into straight or curve lines. F5 - - - true - - - - :/images/themes/default/mActionTextAnnotation.svg:/images/themes/default/mActionTextAnnotation.svg - - - Text Annotation - - true From 3ab862c1a44f0b7bdaa1f20bddbd582869482945 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 19 Aug 2024 21:06:49 +1000 Subject: [PATCH 08/15] Make common base class for annotation items which render in rect And avoid a bunch of duplicate code --- .../auto_additions/qgsannotationrectitem.py | 5 + .../annotations/qgsannotationitem.sip.in | 6 +- .../qgsannotationpictureitem.sip.in | 174 +--- .../qgsannotationrectangletextitem.sip.in | 167 +--- .../annotations/qgsannotationrectitem.sip.in | 231 ++++++ python/PyQt6/core/core_auto.sip | 1 + .../auto_additions/qgsannotationrectitem.py | 5 + .../annotations/qgsannotationitem.sip.in | 6 +- .../qgsannotationpictureitem.sip.in | 174 +--- .../qgsannotationrectangletextitem.sip.in | 167 +--- .../annotations/qgsannotationrectitem.sip.in | 231 ++++++ python/core/core_auto.sip | 1 + src/core/CMakeLists.txt | 2 + src/core/annotations/qgsannotationitem.h | 6 +- .../annotations/qgsannotationpictureitem.cpp | 726 +--------------- .../annotations/qgsannotationpictureitem.h | 168 +--- .../qgsannotationrectangletextitem.cpp | 706 +--------------- .../qgsannotationrectangletextitem.h | 160 +--- .../annotations/qgsannotationrectitem.cpp | 774 ++++++++++++++++++ src/core/annotations/qgsannotationrectitem.h | 227 +++++ 20 files changed, 1529 insertions(+), 2408 deletions(-) create mode 100644 python/PyQt6/core/auto_additions/qgsannotationrectitem.py create mode 100644 python/PyQt6/core/auto_generated/annotations/qgsannotationrectitem.sip.in create mode 100644 python/core/auto_additions/qgsannotationrectitem.py create mode 100644 python/core/auto_generated/annotations/qgsannotationrectitem.sip.in create mode 100644 src/core/annotations/qgsannotationrectitem.cpp create mode 100644 src/core/annotations/qgsannotationrectitem.h diff --git a/python/PyQt6/core/auto_additions/qgsannotationrectitem.py b/python/PyQt6/core/auto_additions/qgsannotationrectitem.py new file mode 100644 index 000000000000..d12c90e351ba --- /dev/null +++ b/python/PyQt6/core/auto_additions/qgsannotationrectitem.py @@ -0,0 +1,5 @@ +# The following has been generated automatically from src/core/annotations/qgsannotationrectitem.h +try: + QgsAnnotationRectItem.__group__ = ['annotations'] +except NameError: + pass diff --git a/python/PyQt6/core/auto_generated/annotations/qgsannotationitem.sip.in b/python/PyQt6/core/auto_generated/annotations/qgsannotationitem.sip.in index 71b8f676f4db..8d554c653cf0 100644 --- a/python/PyQt6/core/auto_generated/annotations/qgsannotationitem.sip.in +++ b/python/PyQt6/core/auto_generated/annotations/qgsannotationitem.sip.in @@ -380,14 +380,14 @@ Sets the ``unit`` for the :py:func:`~QgsAnnotationItem.offsetFromCallout`. protected: - void copyCommonProperties( const QgsAnnotationItem *other ); + virtual void copyCommonProperties( const QgsAnnotationItem *other ); %Docstring Copies common properties from the base class from an ``other`` item. .. versionadded:: 3.22 %End - bool writeCommonProperties( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const; + virtual bool writeCommonProperties( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const; %Docstring Writes common properties from the base class into an XML ``element``. @@ -396,7 +396,7 @@ Writes common properties from the base class into an XML ``element``. .. versionadded:: 3.22 %End - bool readCommonProperties( const QDomElement &element, const QgsReadWriteContext &context ); + virtual bool readCommonProperties( const QDomElement &element, const QgsReadWriteContext &context ); %Docstring Reads common properties from the base class from the given DOM ``element``. diff --git a/python/PyQt6/core/auto_generated/annotations/qgsannotationpictureitem.sip.in b/python/PyQt6/core/auto_generated/annotations/qgsannotationpictureitem.sip.in index f5d560487d4d..efab04ed894e 100644 --- a/python/PyQt6/core/auto_generated/annotations/qgsannotationpictureitem.sip.in +++ b/python/PyQt6/core/auto_generated/annotations/qgsannotationpictureitem.sip.in @@ -9,7 +9,7 @@ -class QgsAnnotationPictureItem : QgsAnnotationItem +class QgsAnnotationPictureItem : QgsAnnotationRectItem { %Docstring(signature="appended") An annotation item which renders a picture. @@ -31,55 +31,16 @@ within the specified ``bounds`` geometry. virtual QString type() const; - virtual Qgis::AnnotationItemFlags flags() const; - - virtual void render( QgsRenderContext &context, QgsFeedback *feedback ); - virtual bool writeXml( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const; - virtual QList< QgsAnnotationItemNode > nodesV2( const QgsAnnotationItemEditContext &context ) const; - - virtual Qgis::AnnotationItemEditOperationResult applyEditV2( QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext &context ); - - virtual QgsAnnotationItemEditOperationTransientResults *transientEditResultsV2( QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext &context ) /Factory/; - - - static QgsAnnotationPictureItem *create() /Factory/; -%Docstring -Creates a new polygon annotation item. -%End - virtual bool readXml( const QDomElement &element, const QgsReadWriteContext &context ); virtual QgsAnnotationPictureItem *clone() const /Factory/; - virtual QgsRectangle boundingBox() const; - - virtual QgsRectangle boundingBox( QgsRenderContext &context ) const; - - QgsRectangle bounds() const; -%Docstring -Returns the bounds of the picture. - -The coordinate reference system for the bounds will be the parent layer's :py:func:`QgsAnnotationLayer.crs()`. - -When the :py:func:`~QgsAnnotationPictureItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize then the picture will be placed -at the center of the bounds. - -.. seealso:: :py:func:`setBounds` -%End - - void setBounds( const QgsRectangle &bounds ); + static QgsAnnotationPictureItem *create() /Factory/; %Docstring -Sets the ``bounds`` of the picture. - -The coordinate reference system for the bounds will be the parent layer's :py:func:`QgsAnnotationLayer.crs()`. - -When the :py:func:`~QgsAnnotationPictureItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize then the picture will be placed -at the center of the bounds. - -.. seealso:: :py:func:`bounds` +Creates a new polygon annotation item. %End QString path() const; @@ -101,60 +62,6 @@ Sets the ``format`` and ``path`` of the image used to render the item. .. seealso:: :py:func:`path` .. seealso:: :py:func:`format` -%End - - Qgis::AnnotationPlacementMode placementMode() const; -%Docstring -Returns the placement mode for the picture. - -.. seealso:: :py:func:`setPlacementMode` -%End - - void setPlacementMode( Qgis::AnnotationPlacementMode mode ); -%Docstring -Sets the placement ``mode`` for the picture. - -.. seealso:: :py:func:`placementMode` -%End - - QSizeF fixedSize() const; -%Docstring -Returns the fixed size to use for the picture, when the :py:func:`~QgsAnnotationPictureItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. - -Units are retrieved via :py:func:`~QgsAnnotationPictureItem.fixedSizeUnit` - -.. seealso:: :py:func:`setFixedSize` - -.. seealso:: :py:func:`fixedSizeUnit` -%End - - void setFixedSize( const QSizeF &size ); -%Docstring -Sets the fixed ``size`` to use for the picture, when the :py:func:`~QgsAnnotationPictureItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. - -Units are set via :py:func:`~QgsAnnotationPictureItem.setFixedSizeUnit` - -.. seealso:: :py:func:`fixedSize` - -.. seealso:: :py:func:`setFixedSizeUnit` -%End - - Qgis::RenderUnit fixedSizeUnit() const; -%Docstring -Returns the units to use for fixed picture sizes, when the :py:func:`~QgsAnnotationPictureItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. - -.. seealso:: :py:func:`setFixedSizeUnit` - -.. seealso:: :py:func:`fixedSize` -%End - - void setFixedSizeUnit( Qgis::RenderUnit unit ); -%Docstring -Sets the ``unit`` to use for fixed picture sizes, when the :py:func:`~QgsAnnotationPictureItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. - -.. seealso:: :py:func:`fixedSizeUnit` - -.. seealso:: :py:func:`setFixedSize` %End bool lockAspectRatio() const; @@ -171,81 +78,10 @@ Sets whether the aspect ratio of the picture will be retained. .. seealso:: :py:func:`lockAspectRatio` %End - bool backgroundEnabled() const; -%Docstring -Returns ``True`` if the item's background should be rendered. - -.. seealso:: :py:func:`setBackgroundEnabled` - -.. seealso:: :py:func:`backgroundSymbol` -%End - - void setBackgroundEnabled( bool enabled ); -%Docstring -Sets whether the item's background should be rendered. - -.. seealso:: :py:func:`backgroundEnabled` - -.. seealso:: :py:func:`setBackgroundSymbol` -%End - - const QgsFillSymbol *backgroundSymbol() const; -%Docstring -Returns the symbol used to render the item's background. - -.. seealso:: :py:func:`backgroundEnabled` - -.. seealso:: :py:func:`setBackgroundSymbol` -%End - - void setBackgroundSymbol( QgsFillSymbol *symbol /Transfer/ ); -%Docstring -Sets the ``symbol`` used to render the item's background. - -The item takes ownership of the symbol. - -.. seealso:: :py:func:`backgroundSymbol` - -.. seealso:: :py:func:`setBackgroundEnabled` -%End - - bool frameEnabled() const; -%Docstring -Returns ``True`` if the item's frame should be rendered. - -.. seealso:: :py:func:`setFrameEnabled` - -.. seealso:: :py:func:`frameSymbol` -%End - - void setFrameEnabled( bool enabled ); -%Docstring -Sets whether the item's frame should be rendered. + protected: -.. seealso:: :py:func:`frameEnabled` + virtual void renderInBounds( QgsRenderContext &context, const QRectF &painterBounds, QgsFeedback *feedback ); -.. seealso:: :py:func:`setFrameSymbol` -%End - - const QgsFillSymbol *frameSymbol() const; -%Docstring -Returns the symbol used to render the item's frame. - -.. seealso:: :py:func:`frameEnabled` - -.. seealso:: :py:func:`setFrameSymbol` -%End - - void setFrameSymbol( QgsFillSymbol *symbol /Transfer/ ); -%Docstring -Sets the ``symbol`` used to render the item's frame. - -The item takes ownership of the symbol. - -.. seealso:: :py:func:`frameSymbol` - -.. seealso:: :py:func:`setBackgroundEnabled` -%End private: QgsAnnotationPictureItem( const QgsAnnotationPictureItem &other ); diff --git a/python/PyQt6/core/auto_generated/annotations/qgsannotationrectangletextitem.sip.in b/python/PyQt6/core/auto_generated/annotations/qgsannotationrectangletextitem.sip.in index 7af569f5bf97..aec515065634 100644 --- a/python/PyQt6/core/auto_generated/annotations/qgsannotationrectangletextitem.sip.in +++ b/python/PyQt6/core/auto_generated/annotations/qgsannotationrectangletextitem.sip.in @@ -9,7 +9,7 @@ -class QgsAnnotationRectangleTextItem : QgsAnnotationItem +class QgsAnnotationRectangleTextItem : QgsAnnotationRectItem { %Docstring(signature="appended") An annotation item which renders paragraphs of text within a rectangle. @@ -33,16 +33,8 @@ within the specified ``bounds`` rectangle. virtual Qgis::AnnotationItemFlags flags() const; - virtual void render( QgsRenderContext &context, QgsFeedback *feedback ); - virtual bool writeXml( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const; - virtual QList< QgsAnnotationItemNode > nodesV2( const QgsAnnotationItemEditContext &context ) const; - - virtual Qgis::AnnotationItemEditOperationResult applyEditV2( QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext &context ); - - virtual QgsAnnotationItemEditOperationTransientResults *transientEditResultsV2( QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext &context ) /Factory/; - static QgsAnnotationRectangleTextItem *create() /Factory/; %Docstring @@ -53,28 +45,6 @@ Creates a new rectangle text annotation item. virtual QgsAnnotationRectangleTextItem *clone() const /Factory/; - virtual QgsRectangle boundingBox() const; - - virtual QgsRectangle boundingBox( QgsRenderContext &context ) const; - - - QgsRectangle bounds() const; -%Docstring -Returns the bounds of the text. - -The coordinate reference system for the bounds will be the parent layer's :py:func:`QgsAnnotationLayer.crs()`. - -.. seealso:: :py:func:`setBounds` -%End - - void setBounds( const QgsRectangle &bounds ); -%Docstring -Sets the ``bounds`` of the text. - -The coordinate reference system for the bounds will be the parent layer's :py:func:`QgsAnnotationLayer.crs()`. - -.. seealso:: :py:func:`bounds` -%End QString text() const; %Docstring @@ -88,60 +58,6 @@ Returns the text rendered by the item. Sets the ``text`` rendered by the item. .. seealso:: :py:func:`text` -%End - - Qgis::AnnotationPlacementMode placementMode() const; -%Docstring -Returns the placement mode for the text. - -.. seealso:: :py:func:`setPlacementMode` -%End - - void setPlacementMode( Qgis::AnnotationPlacementMode mode ); -%Docstring -Sets the placement ``mode`` for the text. - -.. seealso:: :py:func:`placementMode` -%End - - QSizeF fixedSize() const; -%Docstring -Returns the fixed size to use for the text, when the :py:func:`~QgsAnnotationRectangleTextItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. - -Units are retrieved via :py:func:`~QgsAnnotationRectangleTextItem.fixedSizeUnit` - -.. seealso:: :py:func:`setFixedSize` - -.. seealso:: :py:func:`fixedSizeUnit` -%End - - void setFixedSize( const QSizeF &size ); -%Docstring -Sets the fixed ``size`` to use for the text, when the :py:func:`~QgsAnnotationRectangleTextItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. - -Units are set via :py:func:`~QgsAnnotationRectangleTextItem.setFixedSizeUnit` - -.. seealso:: :py:func:`fixedSize` - -.. seealso:: :py:func:`setFixedSizeUnit` -%End - - Qgis::RenderUnit fixedSizeUnit() const; -%Docstring -Returns the units to use for fixed text sizes, when the :py:func:`~QgsAnnotationRectangleTextItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. - -.. seealso:: :py:func:`setFixedSizeUnit` - -.. seealso:: :py:func:`fixedSize` -%End - - void setFixedSizeUnit( Qgis::RenderUnit unit ); -%Docstring -Sets the ``unit`` to use for fixed text sizes, when the :py:func:`~QgsAnnotationRectangleTextItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. - -.. seealso:: :py:func:`fixedSizeUnit` - -.. seealso:: :py:func:`setFixedSize` %End QgsTextFormat format() const; @@ -170,82 +86,6 @@ Returns the text's alignment relative to the :py:func:`~QgsAnnotationRectangleTe Sets the text's ``alignment`` relative to the :py:func:`~QgsAnnotationRectangleTextItem.bounds` rectangle. .. seealso:: :py:func:`alignment` -%End - - bool backgroundEnabled() const; -%Docstring -Returns ``True`` if the item's background should be rendered. - -.. seealso:: :py:func:`setBackgroundEnabled` - -.. seealso:: :py:func:`backgroundSymbol` -%End - - void setBackgroundEnabled( bool enabled ); -%Docstring -Sets whether the item's background should be rendered. - -.. seealso:: :py:func:`backgroundEnabled` - -.. seealso:: :py:func:`setBackgroundSymbol` -%End - - const QgsFillSymbol *backgroundSymbol() const; -%Docstring -Returns the symbol used to render the item's background. - -.. seealso:: :py:func:`backgroundEnabled` - -.. seealso:: :py:func:`setBackgroundSymbol` -%End - - void setBackgroundSymbol( QgsFillSymbol *symbol /Transfer/ ); -%Docstring -Sets the ``symbol`` used to render the item's background. - -The item takes ownership of the symbol. - -.. seealso:: :py:func:`backgroundSymbol` - -.. seealso:: :py:func:`setBackgroundEnabled` -%End - - bool frameEnabled() const; -%Docstring -Returns ``True`` if the item's frame should be rendered. - -.. seealso:: :py:func:`setFrameEnabled` - -.. seealso:: :py:func:`frameSymbol` -%End - - void setFrameEnabled( bool enabled ); -%Docstring -Sets whether the item's frame should be rendered. - -.. seealso:: :py:func:`frameEnabled` - -.. seealso:: :py:func:`setFrameSymbol` -%End - - const QgsFillSymbol *frameSymbol() const; -%Docstring -Returns the symbol used to render the item's frame. - -.. seealso:: :py:func:`frameEnabled` - -.. seealso:: :py:func:`setFrameSymbol` -%End - - void setFrameSymbol( QgsFillSymbol *symbol /Transfer/ ); -%Docstring -Sets the ``symbol`` used to render the item's frame. - -The item takes ownership of the symbol. - -.. seealso:: :py:func:`frameSymbol` - -.. seealso:: :py:func:`setBackgroundEnabled` %End const QgsMargins &margins() const; @@ -288,6 +128,11 @@ Returns the units for the margins between the item's frame and the interior text .. seealso:: :py:func:`margins` %End + protected: + + virtual void renderInBounds( QgsRenderContext &context, const QRectF &painterBounds, QgsFeedback *feedback ); + + private: QgsAnnotationRectangleTextItem( const QgsAnnotationRectangleTextItem &other ); }; diff --git a/python/PyQt6/core/auto_generated/annotations/qgsannotationrectitem.sip.in b/python/PyQt6/core/auto_generated/annotations/qgsannotationrectitem.sip.in new file mode 100644 index 000000000000..4b530bccde36 --- /dev/null +++ b/python/PyQt6/core/auto_generated/annotations/qgsannotationrectitem.sip.in @@ -0,0 +1,231 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/annotations/qgsannotationrectitem.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.py again * + ************************************************************************/ + + + + +class QgsAnnotationRectItem : QgsAnnotationItem +{ +%Docstring(signature="appended") +Abstract base class for annotation items which render annotations in a rectangular shape. + +Subclasses should implement the pure virtual :py:func:`~render` method which takes a painter bounds argument. + +.. versionadded:: 3.40 +%End + +%TypeHeaderCode +#include "qgsannotationrectitem.h" +%End + public: + + QgsAnnotationRectItem( const QgsRectangle &bounds ); +%Docstring +Constructor for QgsAnnotationRectItem, rendering the annotation +within the specified ``bounds`` geometry. +%End + ~QgsAnnotationRectItem(); + + virtual Qgis::AnnotationItemFlags flags() const; + + virtual void render( QgsRenderContext &context, QgsFeedback *feedback ); + + virtual QList< QgsAnnotationItemNode > nodesV2( const QgsAnnotationItemEditContext &context ) const; + + virtual Qgis::AnnotationItemEditOperationResult applyEditV2( QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext &context ); + + virtual QgsAnnotationItemEditOperationTransientResults *transientEditResultsV2( QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext &context ) /Factory/; + + virtual QgsRectangle boundingBox() const; + + virtual QgsRectangle boundingBox( QgsRenderContext &context ) const; + + + QgsRectangle bounds() const; +%Docstring +Returns the bounds of the item. + +The coordinate reference system for the item will be the parent layer's :py:func:`QgsAnnotationLayer.crs()`. + +When the :py:func:`~QgsAnnotationRectItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize then the item will be placed +at the center of the bounds. + +.. seealso:: :py:func:`setBounds` +%End + + void setBounds( const QgsRectangle &bounds ); +%Docstring +Sets the ``bounds`` of the item. + +The coordinate reference system for the bounds will be the parent layer's :py:func:`QgsAnnotationLayer.crs()`. + +When the :py:func:`~QgsAnnotationRectItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize then the item will be placed +at the center of the bounds. + +.. seealso:: :py:func:`bounds` +%End + + Qgis::AnnotationPlacementMode placementMode() const; +%Docstring +Returns the placement mode for the item. + +.. seealso:: :py:func:`setPlacementMode` +%End + + void setPlacementMode( Qgis::AnnotationPlacementMode mode ); +%Docstring +Sets the placement ``mode`` for the item. + +.. seealso:: :py:func:`placementMode` +%End + + QSizeF fixedSize() const; +%Docstring +Returns the fixed size to use for the item, when the :py:func:`~QgsAnnotationRectItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. + +Units are retrieved via :py:func:`~QgsAnnotationRectItem.fixedSizeUnit` + +.. seealso:: :py:func:`setFixedSize` + +.. seealso:: :py:func:`fixedSizeUnit` +%End + + void setFixedSize( const QSizeF &size ); +%Docstring +Sets the fixed ``size`` to use for the item, when the :py:func:`~QgsAnnotationRectItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. + +Units are set via :py:func:`~QgsAnnotationRectItem.setFixedSizeUnit` + +.. seealso:: :py:func:`fixedSize` + +.. seealso:: :py:func:`setFixedSizeUnit` +%End + + Qgis::RenderUnit fixedSizeUnit() const; +%Docstring +Returns the units to use for fixed item sizes, when the :py:func:`~QgsAnnotationRectItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. + +.. seealso:: :py:func:`setFixedSizeUnit` + +.. seealso:: :py:func:`fixedSize` +%End + + void setFixedSizeUnit( Qgis::RenderUnit unit ); +%Docstring +Sets the ``unit`` to use for fixed item sizes, when the :py:func:`~QgsAnnotationRectItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. + +.. seealso:: :py:func:`fixedSizeUnit` + +.. seealso:: :py:func:`setFixedSize` +%End + + bool backgroundEnabled() const; +%Docstring +Returns ``True`` if the item's background should be rendered. + +.. seealso:: :py:func:`setBackgroundEnabled` + +.. seealso:: :py:func:`backgroundSymbol` +%End + + void setBackgroundEnabled( bool enabled ); +%Docstring +Sets whether the item's background should be rendered. + +.. seealso:: :py:func:`backgroundEnabled` + +.. seealso:: :py:func:`setBackgroundSymbol` +%End + + const QgsFillSymbol *backgroundSymbol() const; +%Docstring +Returns the symbol used to render the item's background. + +.. seealso:: :py:func:`backgroundEnabled` + +.. seealso:: :py:func:`setBackgroundSymbol` +%End + + void setBackgroundSymbol( QgsFillSymbol *symbol /Transfer/ ); +%Docstring +Sets the ``symbol`` used to render the item's background. + +The item takes ownership of the symbol. + +.. seealso:: :py:func:`backgroundSymbol` + +.. seealso:: :py:func:`setBackgroundEnabled` +%End + + bool frameEnabled() const; +%Docstring +Returns ``True`` if the item's frame should be rendered. + +.. seealso:: :py:func:`setFrameEnabled` + +.. seealso:: :py:func:`frameSymbol` +%End + + void setFrameEnabled( bool enabled ); +%Docstring +Sets whether the item's frame should be rendered. + +.. seealso:: :py:func:`frameEnabled` + +.. seealso:: :py:func:`setFrameSymbol` +%End + + const QgsFillSymbol *frameSymbol() const; +%Docstring +Returns the symbol used to render the item's frame. + +.. seealso:: :py:func:`frameEnabled` + +.. seealso:: :py:func:`setFrameSymbol` +%End + + void setFrameSymbol( QgsFillSymbol *symbol /Transfer/ ); +%Docstring +Sets the ``symbol`` used to render the item's frame. + +The item takes ownership of the symbol. + +.. seealso:: :py:func:`frameSymbol` + +.. seealso:: :py:func:`setBackgroundEnabled` +%End + + protected: + virtual void copyCommonProperties( const QgsAnnotationItem *other ); + + virtual bool writeCommonProperties( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const; + + virtual bool readCommonProperties( const QDomElement &element, const QgsReadWriteContext &context ); + + + virtual void renderInBounds( QgsRenderContext &context, const QRectF &painterRect, QgsFeedback *feedback ) = 0; +%Docstring +Renders the item to the specified render ``context``. + +The ``painterRect`` argument specifies the bounds in painter units where the rectangular +item should be rendered within. + +The ``feedback`` argument can be used to detect render cancellations during expensive +render operations. +%End + + private: + QgsAnnotationRectItem( const QgsAnnotationRectItem &other ); +}; +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/annotations/qgsannotationrectitem.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.py again * + ************************************************************************/ diff --git a/python/PyQt6/core/core_auto.sip b/python/PyQt6/core/core_auto.sip index 5d5d1a31b989..0b01ea61216c 100644 --- a/python/PyQt6/core/core_auto.sip +++ b/python/PyQt6/core/core_auto.sip @@ -236,6 +236,7 @@ %Include auto_generated/annotations/qgsannotationpointtextitem.sip %Include auto_generated/annotations/qgsannotationpolygonitem.sip %Include auto_generated/annotations/qgsannotationrectangletextitem.sip +%Include auto_generated/annotations/qgsannotationrectitem.sip %Include auto_generated/annotations/qgshtmlannotation.sip %Include auto_generated/annotations/qgsrenderedannotationitemdetails.sip %Include auto_generated/annotations/qgssvgannotation.sip diff --git a/python/core/auto_additions/qgsannotationrectitem.py b/python/core/auto_additions/qgsannotationrectitem.py new file mode 100644 index 000000000000..d12c90e351ba --- /dev/null +++ b/python/core/auto_additions/qgsannotationrectitem.py @@ -0,0 +1,5 @@ +# The following has been generated automatically from src/core/annotations/qgsannotationrectitem.h +try: + QgsAnnotationRectItem.__group__ = ['annotations'] +except NameError: + pass diff --git a/python/core/auto_generated/annotations/qgsannotationitem.sip.in b/python/core/auto_generated/annotations/qgsannotationitem.sip.in index 71b8f676f4db..8d554c653cf0 100644 --- a/python/core/auto_generated/annotations/qgsannotationitem.sip.in +++ b/python/core/auto_generated/annotations/qgsannotationitem.sip.in @@ -380,14 +380,14 @@ Sets the ``unit`` for the :py:func:`~QgsAnnotationItem.offsetFromCallout`. protected: - void copyCommonProperties( const QgsAnnotationItem *other ); + virtual void copyCommonProperties( const QgsAnnotationItem *other ); %Docstring Copies common properties from the base class from an ``other`` item. .. versionadded:: 3.22 %End - bool writeCommonProperties( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const; + virtual bool writeCommonProperties( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const; %Docstring Writes common properties from the base class into an XML ``element``. @@ -396,7 +396,7 @@ Writes common properties from the base class into an XML ``element``. .. versionadded:: 3.22 %End - bool readCommonProperties( const QDomElement &element, const QgsReadWriteContext &context ); + virtual bool readCommonProperties( const QDomElement &element, const QgsReadWriteContext &context ); %Docstring Reads common properties from the base class from the given DOM ``element``. diff --git a/python/core/auto_generated/annotations/qgsannotationpictureitem.sip.in b/python/core/auto_generated/annotations/qgsannotationpictureitem.sip.in index f5d560487d4d..efab04ed894e 100644 --- a/python/core/auto_generated/annotations/qgsannotationpictureitem.sip.in +++ b/python/core/auto_generated/annotations/qgsannotationpictureitem.sip.in @@ -9,7 +9,7 @@ -class QgsAnnotationPictureItem : QgsAnnotationItem +class QgsAnnotationPictureItem : QgsAnnotationRectItem { %Docstring(signature="appended") An annotation item which renders a picture. @@ -31,55 +31,16 @@ within the specified ``bounds`` geometry. virtual QString type() const; - virtual Qgis::AnnotationItemFlags flags() const; - - virtual void render( QgsRenderContext &context, QgsFeedback *feedback ); - virtual bool writeXml( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const; - virtual QList< QgsAnnotationItemNode > nodesV2( const QgsAnnotationItemEditContext &context ) const; - - virtual Qgis::AnnotationItemEditOperationResult applyEditV2( QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext &context ); - - virtual QgsAnnotationItemEditOperationTransientResults *transientEditResultsV2( QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext &context ) /Factory/; - - - static QgsAnnotationPictureItem *create() /Factory/; -%Docstring -Creates a new polygon annotation item. -%End - virtual bool readXml( const QDomElement &element, const QgsReadWriteContext &context ); virtual QgsAnnotationPictureItem *clone() const /Factory/; - virtual QgsRectangle boundingBox() const; - - virtual QgsRectangle boundingBox( QgsRenderContext &context ) const; - - QgsRectangle bounds() const; -%Docstring -Returns the bounds of the picture. - -The coordinate reference system for the bounds will be the parent layer's :py:func:`QgsAnnotationLayer.crs()`. - -When the :py:func:`~QgsAnnotationPictureItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize then the picture will be placed -at the center of the bounds. - -.. seealso:: :py:func:`setBounds` -%End - - void setBounds( const QgsRectangle &bounds ); + static QgsAnnotationPictureItem *create() /Factory/; %Docstring -Sets the ``bounds`` of the picture. - -The coordinate reference system for the bounds will be the parent layer's :py:func:`QgsAnnotationLayer.crs()`. - -When the :py:func:`~QgsAnnotationPictureItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize then the picture will be placed -at the center of the bounds. - -.. seealso:: :py:func:`bounds` +Creates a new polygon annotation item. %End QString path() const; @@ -101,60 +62,6 @@ Sets the ``format`` and ``path`` of the image used to render the item. .. seealso:: :py:func:`path` .. seealso:: :py:func:`format` -%End - - Qgis::AnnotationPlacementMode placementMode() const; -%Docstring -Returns the placement mode for the picture. - -.. seealso:: :py:func:`setPlacementMode` -%End - - void setPlacementMode( Qgis::AnnotationPlacementMode mode ); -%Docstring -Sets the placement ``mode`` for the picture. - -.. seealso:: :py:func:`placementMode` -%End - - QSizeF fixedSize() const; -%Docstring -Returns the fixed size to use for the picture, when the :py:func:`~QgsAnnotationPictureItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. - -Units are retrieved via :py:func:`~QgsAnnotationPictureItem.fixedSizeUnit` - -.. seealso:: :py:func:`setFixedSize` - -.. seealso:: :py:func:`fixedSizeUnit` -%End - - void setFixedSize( const QSizeF &size ); -%Docstring -Sets the fixed ``size`` to use for the picture, when the :py:func:`~QgsAnnotationPictureItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. - -Units are set via :py:func:`~QgsAnnotationPictureItem.setFixedSizeUnit` - -.. seealso:: :py:func:`fixedSize` - -.. seealso:: :py:func:`setFixedSizeUnit` -%End - - Qgis::RenderUnit fixedSizeUnit() const; -%Docstring -Returns the units to use for fixed picture sizes, when the :py:func:`~QgsAnnotationPictureItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. - -.. seealso:: :py:func:`setFixedSizeUnit` - -.. seealso:: :py:func:`fixedSize` -%End - - void setFixedSizeUnit( Qgis::RenderUnit unit ); -%Docstring -Sets the ``unit`` to use for fixed picture sizes, when the :py:func:`~QgsAnnotationPictureItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. - -.. seealso:: :py:func:`fixedSizeUnit` - -.. seealso:: :py:func:`setFixedSize` %End bool lockAspectRatio() const; @@ -171,81 +78,10 @@ Sets whether the aspect ratio of the picture will be retained. .. seealso:: :py:func:`lockAspectRatio` %End - bool backgroundEnabled() const; -%Docstring -Returns ``True`` if the item's background should be rendered. - -.. seealso:: :py:func:`setBackgroundEnabled` - -.. seealso:: :py:func:`backgroundSymbol` -%End - - void setBackgroundEnabled( bool enabled ); -%Docstring -Sets whether the item's background should be rendered. - -.. seealso:: :py:func:`backgroundEnabled` - -.. seealso:: :py:func:`setBackgroundSymbol` -%End - - const QgsFillSymbol *backgroundSymbol() const; -%Docstring -Returns the symbol used to render the item's background. - -.. seealso:: :py:func:`backgroundEnabled` - -.. seealso:: :py:func:`setBackgroundSymbol` -%End - - void setBackgroundSymbol( QgsFillSymbol *symbol /Transfer/ ); -%Docstring -Sets the ``symbol`` used to render the item's background. - -The item takes ownership of the symbol. - -.. seealso:: :py:func:`backgroundSymbol` - -.. seealso:: :py:func:`setBackgroundEnabled` -%End - - bool frameEnabled() const; -%Docstring -Returns ``True`` if the item's frame should be rendered. - -.. seealso:: :py:func:`setFrameEnabled` - -.. seealso:: :py:func:`frameSymbol` -%End - - void setFrameEnabled( bool enabled ); -%Docstring -Sets whether the item's frame should be rendered. + protected: -.. seealso:: :py:func:`frameEnabled` + virtual void renderInBounds( QgsRenderContext &context, const QRectF &painterBounds, QgsFeedback *feedback ); -.. seealso:: :py:func:`setFrameSymbol` -%End - - const QgsFillSymbol *frameSymbol() const; -%Docstring -Returns the symbol used to render the item's frame. - -.. seealso:: :py:func:`frameEnabled` - -.. seealso:: :py:func:`setFrameSymbol` -%End - - void setFrameSymbol( QgsFillSymbol *symbol /Transfer/ ); -%Docstring -Sets the ``symbol`` used to render the item's frame. - -The item takes ownership of the symbol. - -.. seealso:: :py:func:`frameSymbol` - -.. seealso:: :py:func:`setBackgroundEnabled` -%End private: QgsAnnotationPictureItem( const QgsAnnotationPictureItem &other ); diff --git a/python/core/auto_generated/annotations/qgsannotationrectangletextitem.sip.in b/python/core/auto_generated/annotations/qgsannotationrectangletextitem.sip.in index 7af569f5bf97..aec515065634 100644 --- a/python/core/auto_generated/annotations/qgsannotationrectangletextitem.sip.in +++ b/python/core/auto_generated/annotations/qgsannotationrectangletextitem.sip.in @@ -9,7 +9,7 @@ -class QgsAnnotationRectangleTextItem : QgsAnnotationItem +class QgsAnnotationRectangleTextItem : QgsAnnotationRectItem { %Docstring(signature="appended") An annotation item which renders paragraphs of text within a rectangle. @@ -33,16 +33,8 @@ within the specified ``bounds`` rectangle. virtual Qgis::AnnotationItemFlags flags() const; - virtual void render( QgsRenderContext &context, QgsFeedback *feedback ); - virtual bool writeXml( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const; - virtual QList< QgsAnnotationItemNode > nodesV2( const QgsAnnotationItemEditContext &context ) const; - - virtual Qgis::AnnotationItemEditOperationResult applyEditV2( QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext &context ); - - virtual QgsAnnotationItemEditOperationTransientResults *transientEditResultsV2( QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext &context ) /Factory/; - static QgsAnnotationRectangleTextItem *create() /Factory/; %Docstring @@ -53,28 +45,6 @@ Creates a new rectangle text annotation item. virtual QgsAnnotationRectangleTextItem *clone() const /Factory/; - virtual QgsRectangle boundingBox() const; - - virtual QgsRectangle boundingBox( QgsRenderContext &context ) const; - - - QgsRectangle bounds() const; -%Docstring -Returns the bounds of the text. - -The coordinate reference system for the bounds will be the parent layer's :py:func:`QgsAnnotationLayer.crs()`. - -.. seealso:: :py:func:`setBounds` -%End - - void setBounds( const QgsRectangle &bounds ); -%Docstring -Sets the ``bounds`` of the text. - -The coordinate reference system for the bounds will be the parent layer's :py:func:`QgsAnnotationLayer.crs()`. - -.. seealso:: :py:func:`bounds` -%End QString text() const; %Docstring @@ -88,60 +58,6 @@ Returns the text rendered by the item. Sets the ``text`` rendered by the item. .. seealso:: :py:func:`text` -%End - - Qgis::AnnotationPlacementMode placementMode() const; -%Docstring -Returns the placement mode for the text. - -.. seealso:: :py:func:`setPlacementMode` -%End - - void setPlacementMode( Qgis::AnnotationPlacementMode mode ); -%Docstring -Sets the placement ``mode`` for the text. - -.. seealso:: :py:func:`placementMode` -%End - - QSizeF fixedSize() const; -%Docstring -Returns the fixed size to use for the text, when the :py:func:`~QgsAnnotationRectangleTextItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. - -Units are retrieved via :py:func:`~QgsAnnotationRectangleTextItem.fixedSizeUnit` - -.. seealso:: :py:func:`setFixedSize` - -.. seealso:: :py:func:`fixedSizeUnit` -%End - - void setFixedSize( const QSizeF &size ); -%Docstring -Sets the fixed ``size`` to use for the text, when the :py:func:`~QgsAnnotationRectangleTextItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. - -Units are set via :py:func:`~QgsAnnotationRectangleTextItem.setFixedSizeUnit` - -.. seealso:: :py:func:`fixedSize` - -.. seealso:: :py:func:`setFixedSizeUnit` -%End - - Qgis::RenderUnit fixedSizeUnit() const; -%Docstring -Returns the units to use for fixed text sizes, when the :py:func:`~QgsAnnotationRectangleTextItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. - -.. seealso:: :py:func:`setFixedSizeUnit` - -.. seealso:: :py:func:`fixedSize` -%End - - void setFixedSizeUnit( Qgis::RenderUnit unit ); -%Docstring -Sets the ``unit`` to use for fixed text sizes, when the :py:func:`~QgsAnnotationRectangleTextItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. - -.. seealso:: :py:func:`fixedSizeUnit` - -.. seealso:: :py:func:`setFixedSize` %End QgsTextFormat format() const; @@ -170,82 +86,6 @@ Returns the text's alignment relative to the :py:func:`~QgsAnnotationRectangleTe Sets the text's ``alignment`` relative to the :py:func:`~QgsAnnotationRectangleTextItem.bounds` rectangle. .. seealso:: :py:func:`alignment` -%End - - bool backgroundEnabled() const; -%Docstring -Returns ``True`` if the item's background should be rendered. - -.. seealso:: :py:func:`setBackgroundEnabled` - -.. seealso:: :py:func:`backgroundSymbol` -%End - - void setBackgroundEnabled( bool enabled ); -%Docstring -Sets whether the item's background should be rendered. - -.. seealso:: :py:func:`backgroundEnabled` - -.. seealso:: :py:func:`setBackgroundSymbol` -%End - - const QgsFillSymbol *backgroundSymbol() const; -%Docstring -Returns the symbol used to render the item's background. - -.. seealso:: :py:func:`backgroundEnabled` - -.. seealso:: :py:func:`setBackgroundSymbol` -%End - - void setBackgroundSymbol( QgsFillSymbol *symbol /Transfer/ ); -%Docstring -Sets the ``symbol`` used to render the item's background. - -The item takes ownership of the symbol. - -.. seealso:: :py:func:`backgroundSymbol` - -.. seealso:: :py:func:`setBackgroundEnabled` -%End - - bool frameEnabled() const; -%Docstring -Returns ``True`` if the item's frame should be rendered. - -.. seealso:: :py:func:`setFrameEnabled` - -.. seealso:: :py:func:`frameSymbol` -%End - - void setFrameEnabled( bool enabled ); -%Docstring -Sets whether the item's frame should be rendered. - -.. seealso:: :py:func:`frameEnabled` - -.. seealso:: :py:func:`setFrameSymbol` -%End - - const QgsFillSymbol *frameSymbol() const; -%Docstring -Returns the symbol used to render the item's frame. - -.. seealso:: :py:func:`frameEnabled` - -.. seealso:: :py:func:`setFrameSymbol` -%End - - void setFrameSymbol( QgsFillSymbol *symbol /Transfer/ ); -%Docstring -Sets the ``symbol`` used to render the item's frame. - -The item takes ownership of the symbol. - -.. seealso:: :py:func:`frameSymbol` - -.. seealso:: :py:func:`setBackgroundEnabled` %End const QgsMargins &margins() const; @@ -288,6 +128,11 @@ Returns the units for the margins between the item's frame and the interior text .. seealso:: :py:func:`margins` %End + protected: + + virtual void renderInBounds( QgsRenderContext &context, const QRectF &painterBounds, QgsFeedback *feedback ); + + private: QgsAnnotationRectangleTextItem( const QgsAnnotationRectangleTextItem &other ); }; diff --git a/python/core/auto_generated/annotations/qgsannotationrectitem.sip.in b/python/core/auto_generated/annotations/qgsannotationrectitem.sip.in new file mode 100644 index 000000000000..4b530bccde36 --- /dev/null +++ b/python/core/auto_generated/annotations/qgsannotationrectitem.sip.in @@ -0,0 +1,231 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/annotations/qgsannotationrectitem.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.py again * + ************************************************************************/ + + + + +class QgsAnnotationRectItem : QgsAnnotationItem +{ +%Docstring(signature="appended") +Abstract base class for annotation items which render annotations in a rectangular shape. + +Subclasses should implement the pure virtual :py:func:`~render` method which takes a painter bounds argument. + +.. versionadded:: 3.40 +%End + +%TypeHeaderCode +#include "qgsannotationrectitem.h" +%End + public: + + QgsAnnotationRectItem( const QgsRectangle &bounds ); +%Docstring +Constructor for QgsAnnotationRectItem, rendering the annotation +within the specified ``bounds`` geometry. +%End + ~QgsAnnotationRectItem(); + + virtual Qgis::AnnotationItemFlags flags() const; + + virtual void render( QgsRenderContext &context, QgsFeedback *feedback ); + + virtual QList< QgsAnnotationItemNode > nodesV2( const QgsAnnotationItemEditContext &context ) const; + + virtual Qgis::AnnotationItemEditOperationResult applyEditV2( QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext &context ); + + virtual QgsAnnotationItemEditOperationTransientResults *transientEditResultsV2( QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext &context ) /Factory/; + + virtual QgsRectangle boundingBox() const; + + virtual QgsRectangle boundingBox( QgsRenderContext &context ) const; + + + QgsRectangle bounds() const; +%Docstring +Returns the bounds of the item. + +The coordinate reference system for the item will be the parent layer's :py:func:`QgsAnnotationLayer.crs()`. + +When the :py:func:`~QgsAnnotationRectItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize then the item will be placed +at the center of the bounds. + +.. seealso:: :py:func:`setBounds` +%End + + void setBounds( const QgsRectangle &bounds ); +%Docstring +Sets the ``bounds`` of the item. + +The coordinate reference system for the bounds will be the parent layer's :py:func:`QgsAnnotationLayer.crs()`. + +When the :py:func:`~QgsAnnotationRectItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize then the item will be placed +at the center of the bounds. + +.. seealso:: :py:func:`bounds` +%End + + Qgis::AnnotationPlacementMode placementMode() const; +%Docstring +Returns the placement mode for the item. + +.. seealso:: :py:func:`setPlacementMode` +%End + + void setPlacementMode( Qgis::AnnotationPlacementMode mode ); +%Docstring +Sets the placement ``mode`` for the item. + +.. seealso:: :py:func:`placementMode` +%End + + QSizeF fixedSize() const; +%Docstring +Returns the fixed size to use for the item, when the :py:func:`~QgsAnnotationRectItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. + +Units are retrieved via :py:func:`~QgsAnnotationRectItem.fixedSizeUnit` + +.. seealso:: :py:func:`setFixedSize` + +.. seealso:: :py:func:`fixedSizeUnit` +%End + + void setFixedSize( const QSizeF &size ); +%Docstring +Sets the fixed ``size`` to use for the item, when the :py:func:`~QgsAnnotationRectItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. + +Units are set via :py:func:`~QgsAnnotationRectItem.setFixedSizeUnit` + +.. seealso:: :py:func:`fixedSize` + +.. seealso:: :py:func:`setFixedSizeUnit` +%End + + Qgis::RenderUnit fixedSizeUnit() const; +%Docstring +Returns the units to use for fixed item sizes, when the :py:func:`~QgsAnnotationRectItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. + +.. seealso:: :py:func:`setFixedSizeUnit` + +.. seealso:: :py:func:`fixedSize` +%End + + void setFixedSizeUnit( Qgis::RenderUnit unit ); +%Docstring +Sets the ``unit`` to use for fixed item sizes, when the :py:func:`~QgsAnnotationRectItem.placementMode` is :py:class:`Qgis`.AnnotationPlacementMode.FixedSize. + +.. seealso:: :py:func:`fixedSizeUnit` + +.. seealso:: :py:func:`setFixedSize` +%End + + bool backgroundEnabled() const; +%Docstring +Returns ``True`` if the item's background should be rendered. + +.. seealso:: :py:func:`setBackgroundEnabled` + +.. seealso:: :py:func:`backgroundSymbol` +%End + + void setBackgroundEnabled( bool enabled ); +%Docstring +Sets whether the item's background should be rendered. + +.. seealso:: :py:func:`backgroundEnabled` + +.. seealso:: :py:func:`setBackgroundSymbol` +%End + + const QgsFillSymbol *backgroundSymbol() const; +%Docstring +Returns the symbol used to render the item's background. + +.. seealso:: :py:func:`backgroundEnabled` + +.. seealso:: :py:func:`setBackgroundSymbol` +%End + + void setBackgroundSymbol( QgsFillSymbol *symbol /Transfer/ ); +%Docstring +Sets the ``symbol`` used to render the item's background. + +The item takes ownership of the symbol. + +.. seealso:: :py:func:`backgroundSymbol` + +.. seealso:: :py:func:`setBackgroundEnabled` +%End + + bool frameEnabled() const; +%Docstring +Returns ``True`` if the item's frame should be rendered. + +.. seealso:: :py:func:`setFrameEnabled` + +.. seealso:: :py:func:`frameSymbol` +%End + + void setFrameEnabled( bool enabled ); +%Docstring +Sets whether the item's frame should be rendered. + +.. seealso:: :py:func:`frameEnabled` + +.. seealso:: :py:func:`setFrameSymbol` +%End + + const QgsFillSymbol *frameSymbol() const; +%Docstring +Returns the symbol used to render the item's frame. + +.. seealso:: :py:func:`frameEnabled` + +.. seealso:: :py:func:`setFrameSymbol` +%End + + void setFrameSymbol( QgsFillSymbol *symbol /Transfer/ ); +%Docstring +Sets the ``symbol`` used to render the item's frame. + +The item takes ownership of the symbol. + +.. seealso:: :py:func:`frameSymbol` + +.. seealso:: :py:func:`setBackgroundEnabled` +%End + + protected: + virtual void copyCommonProperties( const QgsAnnotationItem *other ); + + virtual bool writeCommonProperties( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const; + + virtual bool readCommonProperties( const QDomElement &element, const QgsReadWriteContext &context ); + + + virtual void renderInBounds( QgsRenderContext &context, const QRectF &painterRect, QgsFeedback *feedback ) = 0; +%Docstring +Renders the item to the specified render ``context``. + +The ``painterRect`` argument specifies the bounds in painter units where the rectangular +item should be rendered within. + +The ``feedback`` argument can be used to detect render cancellations during expensive +render operations. +%End + + private: + QgsAnnotationRectItem( const QgsAnnotationRectItem &other ); +}; +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/annotations/qgsannotationrectitem.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.py again * + ************************************************************************/ diff --git a/python/core/core_auto.sip b/python/core/core_auto.sip index 5d5d1a31b989..0b01ea61216c 100644 --- a/python/core/core_auto.sip +++ b/python/core/core_auto.sip @@ -236,6 +236,7 @@ %Include auto_generated/annotations/qgsannotationpointtextitem.sip %Include auto_generated/annotations/qgsannotationpolygonitem.sip %Include auto_generated/annotations/qgsannotationrectangletextitem.sip +%Include auto_generated/annotations/qgsannotationrectitem.sip %Include auto_generated/annotations/qgshtmlannotation.sip %Include auto_generated/annotations/qgsrenderedannotationitemdetails.sip %Include auto_generated/annotations/qgssvgannotation.sip diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 4622dbdb6832..fecf4fb64dbd 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -224,6 +224,7 @@ set(QGIS_CORE_SRCS annotations/qgsannotationpointtextitem.cpp annotations/qgsannotationpolygonitem.cpp annotations/qgsannotationrectangletextitem.cpp + annotations/qgsannotationrectitem.cpp annotations/qgshtmlannotation.cpp annotations/qgsrenderedannotationitemdetails.cpp annotations/qgssvgannotation.cpp @@ -1344,6 +1345,7 @@ set(QGIS_CORE_HDRS annotations/qgsannotationpointtextitem.h annotations/qgsannotationpolygonitem.h annotations/qgsannotationrectangletextitem.h + annotations/qgsannotationrectitem.h annotations/qgsannotationregistry.h annotations/qgshtmlannotation.h annotations/qgsrenderedannotationitemdetails.h diff --git a/src/core/annotations/qgsannotationitem.h b/src/core/annotations/qgsannotationitem.h index fe8c17ac2760..e79cef0dff66 100644 --- a/src/core/annotations/qgsannotationitem.h +++ b/src/core/annotations/qgsannotationitem.h @@ -389,7 +389,7 @@ class CORE_EXPORT QgsAnnotationItem * * \since QGIS 3.22 */ - void copyCommonProperties( const QgsAnnotationItem *other ); + virtual void copyCommonProperties( const QgsAnnotationItem *other ); /** * Writes common properties from the base class into an XML \a element. @@ -397,7 +397,7 @@ class CORE_EXPORT QgsAnnotationItem * \see writeXml() * \since QGIS 3.22 */ - bool writeCommonProperties( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const; + virtual bool writeCommonProperties( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const; /** * Reads common properties from the base class from the given DOM \a element. @@ -405,7 +405,7 @@ class CORE_EXPORT QgsAnnotationItem * \see readXml() * \since QGIS 3.22 */ - bool readCommonProperties( const QDomElement &element, const QgsReadWriteContext &context ); + virtual bool readCommonProperties( const QDomElement &element, const QgsReadWriteContext &context ); /** * Renders the item's callout. diff --git a/src/core/annotations/qgsannotationpictureitem.cpp b/src/core/annotations/qgsannotationpictureitem.cpp index e31e846f2ad8..71c0450c9e54 100644 --- a/src/core/annotations/qgsannotationpictureitem.cpp +++ b/src/core/annotations/qgsannotationpictureitem.cpp @@ -21,35 +21,16 @@ #include "qgssvgcache.h" #include "qgsgeometry.h" #include "qgsrendercontext.h" -#include "qgsannotationitemnode.h" -#include "qgsannotationitemeditoperation.h" #include "qgspainting.h" -#include "qgsfillsymbol.h" #include "qgssymbollayerutils.h" -#include "qgsfillsymbollayer.h" -#include "qgslinesymbollayer.h" -#include "qgsunittypes.h" #include "qgscalloutsregistry.h" -#include "qgslinestring.h" -#include "qgspolygon.h" #include QgsAnnotationPictureItem::QgsAnnotationPictureItem( Qgis::PictureFormat format, const QString &path, const QgsRectangle &bounds ) - : QgsAnnotationItem() - , mBounds( bounds ) + : QgsAnnotationRectItem( bounds ) { setPath( format, path ); - - mBackgroundSymbol = std::make_unique< QgsFillSymbol >( - QgsSymbolLayerList - { - new QgsSimpleFillSymbolLayer( QColor( 255, 255, 255 ), Qt::BrushStyle::SolidPattern, QColor( 0, 0, 0 ), Qt::PenStyle::NoPen ) - } - ); - QgsSimpleLineSymbolLayer *borderSymbol = new QgsSimpleLineSymbolLayer( QColor( 0, 0, 0 ) ); - borderSymbol->setPenJoinStyle( Qt::MiterJoin ); - mFrameSymbol = std::make_unique< QgsFillSymbol >( QgsSymbolLayerList{ borderSymbol } ); } QgsAnnotationPictureItem::~QgsAnnotationPictureItem() = default; @@ -59,116 +40,9 @@ QString QgsAnnotationPictureItem::type() const return QStringLiteral( "picture" ); } -Qgis::AnnotationItemFlags QgsAnnotationPictureItem::flags() const -{ - switch ( mPlacementMode ) - { - case Qgis::AnnotationPlacementMode::SpatialBounds: - return Qgis::AnnotationItemFlag::SupportsCallouts; - case Qgis::AnnotationPlacementMode::FixedSize: - return Qgis::AnnotationItemFlag::ScaleDependentBoundingBox - | Qgis::AnnotationItemFlag::SupportsCallouts; - case Qgis::AnnotationPlacementMode::RelativeToMapFrame: - return Qgis::AnnotationItemFlag::ScaleDependentBoundingBox; - } - BUILTIN_UNREACHABLE -} - -void QgsAnnotationPictureItem::render( QgsRenderContext &context, QgsFeedback *feedback ) +void QgsAnnotationPictureItem::renderInBounds( QgsRenderContext &context, const QRectF &painterBounds, QgsFeedback * ) { - QgsRectangle bounds = mBounds; - if ( mPlacementMode != Qgis::AnnotationPlacementMode::RelativeToMapFrame && context.coordinateTransform().isValid() ) - { - try - { - bounds = context.coordinateTransform().transformBoundingBox( mBounds ); - } - catch ( QgsCsException & ) - { - return; - } - } - bool lockAspectRatio = mLockAspectRatio; - QRectF painterBounds; - - switch ( mPlacementMode ) - { - case Qgis::AnnotationPlacementMode::SpatialBounds: - painterBounds = context.mapToPixel().transformBounds( bounds.toRectF() ); - break; - - case Qgis::AnnotationPlacementMode::FixedSize: - { - const double widthPixels = context.convertToPainterUnits( mFixedSize.width(), mFixedSizeUnit ); - const double heightPixels = context.convertToPainterUnits( mFixedSize.height(), mFixedSizeUnit ); - - 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; - } - - case Qgis::AnnotationPlacementMode::RelativeToMapFrame: - { - const double widthPixels = context.convertToPainterUnits( mFixedSize.width(), mFixedSizeUnit ); - const double heightPixels = context.convertToPainterUnits( mFixedSize.height(), mFixedSizeUnit ); - - QPointF center = bounds.center().toQPointF(); - center.rx() *= context.outputSize().width(); - center.ry() *= context.outputSize().height(); - - painterBounds = QRectF( center.x() - widthPixels * 0.5, - center.y() - heightPixels * 0.5, - widthPixels, heightPixels ); - break; - } - } - - if ( painterBounds.width() < 1 || painterBounds.height() < 1 ) - return; - - if ( mDrawBackground && mBackgroundSymbol ) - { - mBackgroundSymbol->startRender( context ); - mBackgroundSymbol->renderPolygon( painterBounds, nullptr, nullptr, context ); - mBackgroundSymbol->stopRender( context ); - } - - if ( mPlacementMode != Qgis::AnnotationPlacementMode::RelativeToMapFrame && callout() ) - { - QgsCallout::QgsCalloutContext calloutContext; - renderCallout( context, painterBounds, 0, calloutContext, feedback ); - } - bool fitsInCache = false; switch ( mFormat ) { @@ -229,379 +103,17 @@ void QgsAnnotationPictureItem::render( QgsRenderContext &context, QgsFeedback *f case Qgis::PictureFormat::Unknown: break; } - - if ( mDrawFrame && mFrameSymbol ) - { - mFrameSymbol->startRender( context ); - mFrameSymbol->renderPolygon( painterBounds, nullptr, nullptr, context ); - mFrameSymbol->stopRender( context ); - } - } bool QgsAnnotationPictureItem::writeXml( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const { element.setAttribute( QStringLiteral( "lockAspect" ), mLockAspectRatio ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ); - element.setAttribute( QStringLiteral( "xMin" ), qgsDoubleToString( mBounds.xMinimum() ) ); - element.setAttribute( QStringLiteral( "xMax" ), qgsDoubleToString( mBounds.xMaximum() ) ); - element.setAttribute( QStringLiteral( "yMin" ), qgsDoubleToString( mBounds.yMinimum() ) ); - element.setAttribute( QStringLiteral( "yMax" ), qgsDoubleToString( mBounds.yMaximum() ) ); element.setAttribute( QStringLiteral( "path" ), mPath ); element.setAttribute( QStringLiteral( "format" ), qgsEnumValueToKey( mFormat ) ); - element.setAttribute( QStringLiteral( "sizeMode" ), qgsEnumValueToKey( mPlacementMode ) ); - element.setAttribute( QStringLiteral( "fixedWidth" ), qgsDoubleToString( mFixedSize.width() ) ); - element.setAttribute( QStringLiteral( "fixedHeight" ), qgsDoubleToString( mFixedSize.height() ) ); - element.setAttribute( QStringLiteral( "fixedSizeUnit" ), QgsUnitTypes::encodeUnit( mFixedSizeUnit ) ); - - element.setAttribute( QStringLiteral( "backgroundEnabled" ), mDrawBackground ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ); - if ( mBackgroundSymbol ) - { - QDomElement backgroundElement = document.createElement( QStringLiteral( "backgroundSymbol" ) ); - backgroundElement.appendChild( QgsSymbolLayerUtils::saveSymbol( QStringLiteral( "backgroundSymbol" ), mBackgroundSymbol.get(), document, context ) ); - element.appendChild( backgroundElement ); - } - - element.setAttribute( QStringLiteral( "frameEnabled" ), mDrawFrame ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ); - if ( mFrameSymbol ) - { - QDomElement frameElement = document.createElement( QStringLiteral( "frameSymbol" ) ); - frameElement.appendChild( QgsSymbolLayerUtils::saveSymbol( QStringLiteral( "frameSymbol" ), mFrameSymbol.get(), document, context ) ); - element.appendChild( frameElement ); - } - writeCommonProperties( element, document, context ); return true; } -QList QgsAnnotationPictureItem::nodesV2( const QgsAnnotationItemEditContext &context ) const -{ - QList res; - switch ( mPlacementMode ) - { - case Qgis::AnnotationPlacementMode::SpatialBounds: - { - res = - { - QgsAnnotationItemNode( QgsVertexId( 0, 0, 0 ), QgsPointXY( mBounds.xMinimum(), mBounds.yMinimum() ), Qgis::AnnotationItemNodeType::VertexHandle ), - QgsAnnotationItemNode( QgsVertexId( 0, 0, 1 ), QgsPointXY( mBounds.xMaximum(), mBounds.yMinimum() ), Qgis::AnnotationItemNodeType::VertexHandle ), - QgsAnnotationItemNode( QgsVertexId( 0, 0, 2 ), QgsPointXY( mBounds.xMaximum(), mBounds.yMaximum() ), Qgis::AnnotationItemNodeType::VertexHandle ), - QgsAnnotationItemNode( QgsVertexId( 0, 0, 3 ), QgsPointXY( mBounds.xMinimum(), mBounds.yMaximum() ), Qgis::AnnotationItemNodeType::VertexHandle ), - }; - - QgsPointXY calloutNodePoint; - if ( !calloutAnchor().isEmpty() ) - { - calloutNodePoint = calloutAnchor().asPoint(); - } - else - { - calloutNodePoint = mBounds.center(); - } - res.append( QgsAnnotationItemNode( QgsVertexId( 1, 0, 0 ), calloutNodePoint, Qgis::AnnotationItemNodeType::CalloutHandle ) ); - - return res; - } - - case Qgis::AnnotationPlacementMode::FixedSize: - { - res = - { - QgsAnnotationItemNode( QgsVertexId( 0, 0, 0 ), mBounds.center(), Qgis::AnnotationItemNodeType::VertexHandle ) - }; - - QgsPointXY calloutNodePoint; - if ( !calloutAnchor().isEmpty() ) - { - calloutNodePoint = calloutAnchor().asPoint(); - } - else - { - calloutNodePoint = QgsPointXY( context.currentItemBounds().xMinimum(), context.currentItemBounds().yMinimum() ); - } - res.append( QgsAnnotationItemNode( QgsVertexId( 1, 0, 0 ), calloutNodePoint, Qgis::AnnotationItemNodeType::CalloutHandle ) ); - - return res; - } - - case Qgis::AnnotationPlacementMode::RelativeToMapFrame: - { - return - { - QgsAnnotationItemNode( QgsVertexId( 0, 0, 0 ), - context.currentItemBounds().center(), Qgis::AnnotationItemNodeType::VertexHandle ) - }; - } - } - BUILTIN_UNREACHABLE -} - -Qgis::AnnotationItemEditOperationResult QgsAnnotationPictureItem::applyEditV2( QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext &context ) -{ - switch ( operation->type() ) - { - case QgsAbstractAnnotationItemEditOperation::Type::MoveNode: - { - QgsAnnotationItemEditOperationMoveNode *moveOperation = dynamic_cast< QgsAnnotationItemEditOperationMoveNode * >( operation ); - if ( moveOperation->nodeId().part == 0 ) - { - switch ( mPlacementMode ) - { - case Qgis::AnnotationPlacementMode::SpatialBounds: - { - switch ( moveOperation->nodeId().vertex ) - { - case 0: - mBounds = QgsRectangle( moveOperation->after().x(), - moveOperation->after().y(), - mBounds.xMaximum(), - mBounds.yMaximum() ); - break; - case 1: - mBounds = QgsRectangle( mBounds.xMinimum(), - moveOperation->after().y(), - moveOperation->after().x(), - mBounds.yMaximum() ); - break; - case 2: - mBounds = QgsRectangle( mBounds.xMinimum(), - mBounds.yMinimum(), - moveOperation->after().x(), - moveOperation->after().y() ); - break; - case 3: - mBounds = QgsRectangle( moveOperation->after().x(), - mBounds.yMinimum(), - mBounds.xMaximum(), - moveOperation->after().y() ); - break; - default: - break; - } - return Qgis::AnnotationItemEditOperationResult::Success; - } - - case Qgis::AnnotationPlacementMode::FixedSize: - { - mBounds = QgsRectangle::fromCenterAndSize( moveOperation->after(), - mBounds.width(), - mBounds.height() ); - return Qgis::AnnotationItemEditOperationResult::Success; - } - - case Qgis::AnnotationPlacementMode::RelativeToMapFrame: - { - const double deltaX = moveOperation->translationXPixels() / context.renderContext().outputSize().width(); - const double deltaY = moveOperation->translationYPixels() / context.renderContext().outputSize().height(); - mBounds = QgsRectangle::fromCenterAndSize( QgsPointXY( mBounds.center().x() + deltaX, mBounds.center().y() + deltaY ), - mBounds.width(), mBounds.height() ); - return Qgis::AnnotationItemEditOperationResult::Success; - } - } - } - else if ( moveOperation->nodeId().part == 1 ) - { - setCalloutAnchor( QgsGeometry::fromPoint( moveOperation->after() ) ); - if ( !callout() ) - { - setCallout( QgsApplication::calloutRegistry()->defaultCallout() ); - } - return Qgis::AnnotationItemEditOperationResult::Success; - } - break; - } - - case QgsAbstractAnnotationItemEditOperation::Type::TranslateItem: - { - QgsAnnotationItemEditOperationTranslateItem *moveOperation = qgis::down_cast< QgsAnnotationItemEditOperationTranslateItem * >( operation ); - switch ( mPlacementMode ) - { - - case Qgis::AnnotationPlacementMode::SpatialBounds: - mBounds = QgsRectangle( mBounds.xMinimum() + moveOperation->translationX(), - mBounds.yMinimum() + moveOperation->translationY(), - mBounds.xMaximum() + moveOperation->translationX(), - mBounds.yMaximum() + moveOperation->translationY() ); - break; - - case Qgis::AnnotationPlacementMode::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; - } - - case Qgis::AnnotationPlacementMode::RelativeToMapFrame: - { - const double deltaX = moveOperation->translationXPixels() / context.renderContext().outputSize().width(); - const double deltaY = moveOperation->translationYPixels() / context.renderContext().outputSize().height(); - mBounds = QgsRectangle::fromCenterAndSize( QgsPointXY( mBounds.center().x() + deltaX, mBounds.center().y() + deltaY ), - mBounds.width(), mBounds.height() ); - break; - } - } - return Qgis::AnnotationItemEditOperationResult::Success; - } - - case QgsAbstractAnnotationItemEditOperation::Type::DeleteNode: - case QgsAbstractAnnotationItemEditOperation::Type::AddNode: - break; - } - return Qgis::AnnotationItemEditOperationResult::Invalid; -} - -QgsAnnotationItemEditOperationTransientResults *QgsAnnotationPictureItem::transientEditResultsV2( QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext &context ) -{ - switch ( operation->type() ) - { - case QgsAbstractAnnotationItemEditOperation::Type::MoveNode: - { - QgsAnnotationItemEditOperationMoveNode *moveOperation = dynamic_cast< QgsAnnotationItemEditOperationMoveNode * >( operation ); - if ( moveOperation->nodeId().part == 0 ) - { - switch ( mPlacementMode ) - { - case Qgis::AnnotationPlacementMode::SpatialBounds: - { - QgsRectangle modifiedBounds = mBounds; - switch ( moveOperation->nodeId().vertex ) - { - case 0: - modifiedBounds.setXMinimum( moveOperation->after().x() ); - modifiedBounds.setYMinimum( moveOperation->after().y() ); - break; - case 1: - modifiedBounds.setXMaximum( moveOperation->after().x() ); - modifiedBounds.setYMinimum( moveOperation->after().y() ); - break; - case 2: - modifiedBounds.setXMaximum( moveOperation->after().x() ); - modifiedBounds.setYMaximum( moveOperation->after().y() ); - break; - case 3: - modifiedBounds.setXMinimum( moveOperation->after().x() ); - modifiedBounds.setYMaximum( moveOperation->after().y() ); - break; - default: - break; - } - return new QgsAnnotationItemEditOperationTransientResults( QgsGeometry::fromRect( modifiedBounds ) ); - } - case Qgis::AnnotationPlacementMode::FixedSize: - { - const QgsRectangle currentBounds = context.currentItemBounds(); - const QgsRectangle newBounds = QgsRectangle::fromCenterAndSize( moveOperation->after(), currentBounds.width(), currentBounds.height() ); - return new QgsAnnotationItemEditOperationTransientResults( QgsGeometry::fromRect( newBounds ) ); - } - case Qgis::AnnotationPlacementMode::RelativeToMapFrame: - { - const QgsRectangle currentBounds = context.currentItemBounds(); - const QgsRectangle newBounds = QgsRectangle::fromCenterAndSize( currentBounds.center() + ( moveOperation->after() - moveOperation->before() ), - currentBounds.width(), currentBounds.height() ); - return new QgsAnnotationItemEditOperationTransientResults( QgsGeometry::fromRect( newBounds ) ); - } - } - } - else - { - QgsAnnotationItemEditOperationMoveNode *moveOperation = dynamic_cast< QgsAnnotationItemEditOperationMoveNode * >( operation ); - return new QgsAnnotationItemEditOperationTransientResults( QgsGeometry( moveOperation->after().clone() ) ); - } - break; - } - - case QgsAbstractAnnotationItemEditOperation::Type::TranslateItem: - { - QgsAnnotationItemEditOperationTranslateItem *moveOperation = qgis::down_cast< QgsAnnotationItemEditOperationTranslateItem * >( operation ); - switch ( mPlacementMode ) - { - case Qgis::AnnotationPlacementMode::SpatialBounds: - { - const QgsRectangle modifiedBounds( mBounds.xMinimum() + moveOperation->translationX(), - mBounds.yMinimum() + moveOperation->translationY(), - mBounds.xMaximum() + moveOperation->translationX(), - mBounds.yMaximum() + moveOperation->translationY() ); - return new QgsAnnotationItemEditOperationTransientResults( QgsGeometry::fromRect( modifiedBounds ) ); - } - - case Qgis::AnnotationPlacementMode::FixedSize: - { - 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( 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 ) ); - } - } - - case Qgis::AnnotationPlacementMode::RelativeToMapFrame: - { - const QgsRectangle currentBounds = context.currentItemBounds(); - const QgsRectangle newBounds = QgsRectangle::fromCenterAndSize( currentBounds.center() + QgsVector( moveOperation->translationX(), moveOperation->translationY() ), - currentBounds.width(), currentBounds.height() ); - return new QgsAnnotationItemEditOperationTransientResults( QgsGeometry::fromRect( newBounds ) ); - } - } - break; - } - - case QgsAbstractAnnotationItemEditOperation::Type::DeleteNode: - case QgsAbstractAnnotationItemEditOperation::Type::AddNode: - break; - } - return nullptr; -} - QgsAnnotationPictureItem *QgsAnnotationPictureItem::create() { return new QgsAnnotationPictureItem( Qgis::PictureFormat::Unknown, QString(), QgsRectangle() ); @@ -610,205 +122,23 @@ QgsAnnotationPictureItem *QgsAnnotationPictureItem::create() bool QgsAnnotationPictureItem::readXml( const QDomElement &element, const QgsReadWriteContext &context ) { mLockAspectRatio = element.attribute( QStringLiteral( "lockAspect" ), QStringLiteral( "1" ) ).toInt(); - mBounds.setXMinimum( element.attribute( QStringLiteral( "xMin" ) ).toDouble() ); - mBounds.setXMaximum( element.attribute( QStringLiteral( "xMax" ) ).toDouble() ); - mBounds.setYMinimum( element.attribute( QStringLiteral( "yMin" ) ).toDouble() ); - mBounds.setYMaximum( element.attribute( QStringLiteral( "yMax" ) ).toDouble() ); const Qgis::PictureFormat format = qgsEnumKeyToValue( element.attribute( QStringLiteral( "format" ) ), Qgis::PictureFormat::Unknown ); setPath( format, element.attribute( QStringLiteral( "path" ) ) ); - mPlacementMode = qgsEnumKeyToValue( element.attribute( QStringLiteral( "sizeMode" ) ), Qgis::AnnotationPlacementMode::SpatialBounds ); - - mFixedSize = QSizeF( - element.attribute( QStringLiteral( "fixedWidth" ) ).toDouble(), - element.attribute( QStringLiteral( "fixedHeight" ) ).toDouble() - ); - mFixedSizeUnit = QgsUnitTypes::decodeRenderUnit( element.attribute( QStringLiteral( "fixedSizeUnit" ) ) ); - - mDrawBackground = element.attribute( QStringLiteral( "backgroundEnabled" ), QStringLiteral( "0" ) ).toInt(); - const QDomElement backgroundSymbolElem = element.firstChildElement( QStringLiteral( "backgroundSymbol" ) ).firstChildElement(); - if ( !backgroundSymbolElem.isNull() ) - { - setBackgroundSymbol( QgsSymbolLayerUtils::loadSymbol< QgsFillSymbol >( backgroundSymbolElem, context ) ); - } - - mDrawFrame = element.attribute( QStringLiteral( "frameEnabled" ), QStringLiteral( "0" ) ).toInt(); - const QDomElement frameSymbolElem = element.firstChildElement( QStringLiteral( "frameSymbol" ) ).firstChildElement(); - if ( !frameSymbolElem.isNull() ) - { - setFrameSymbol( QgsSymbolLayerUtils::loadSymbol< QgsFillSymbol >( frameSymbolElem, context ) ); - } - readCommonProperties( element, context ); return true; } QgsAnnotationPictureItem *QgsAnnotationPictureItem::clone() const { - std::unique_ptr< QgsAnnotationPictureItem > item = std::make_unique< QgsAnnotationPictureItem >( mFormat, mPath, mBounds ); + std::unique_ptr< QgsAnnotationPictureItem > item = std::make_unique< QgsAnnotationPictureItem >( mFormat, mPath, bounds() ); item->setLockAspectRatio( mLockAspectRatio ); - item->setPlacementMode( mPlacementMode ); - item->setFixedSize( mFixedSize ); - item->setFixedSizeUnit( mFixedSizeUnit ); - - item->setBackgroundEnabled( mDrawBackground ); - if ( mBackgroundSymbol ) - item->setBackgroundSymbol( mBackgroundSymbol->clone() ); - - item->setFrameEnabled( mDrawFrame ); - if ( mFrameSymbol ) - item->setFrameSymbol( mFrameSymbol->clone() ); item->copyCommonProperties( this ); return item.release(); } -QgsRectangle QgsAnnotationPictureItem::boundingBox() const -{ - QgsRectangle bounds; - switch ( mPlacementMode ) - { - case Qgis::AnnotationPlacementMode::SpatialBounds: - { - bounds = mBounds; - if ( callout() && !calloutAnchor().isEmpty() ) - { - QgsGeometry anchor = calloutAnchor(); - bounds.combineExtentWith( anchor.boundingBox() ); - } - break; - } - - case Qgis::AnnotationPlacementMode::FixedSize: - if ( callout() && !calloutAnchor().isEmpty() ) - { - bounds = calloutAnchor().boundingBox(); - } - else - { - bounds = QgsRectangle( mBounds.center(), mBounds.center() ); - } - break; - - case Qgis::AnnotationPlacementMode::RelativeToMapFrame: - bounds = mBounds; - break; - } - - return bounds; -} - -QgsRectangle QgsAnnotationPictureItem::boundingBox( QgsRenderContext &context ) const -{ - switch ( mPlacementMode ) - { - case Qgis::AnnotationPlacementMode::SpatialBounds: - return QgsAnnotationPictureItem::boundingBox(); - - case Qgis::AnnotationPlacementMode::FixedSize: - { - const double widthPixels = context.convertToPainterUnits( mFixedSize.width(), mFixedSizeUnit ); - const double heightPixels = context.convertToPainterUnits( mFixedSize.height(), mFixedSizeUnit ); - - 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() ); - const QgsPointXY bottomRight = context.mapToPixel().toMapCoordinates( boundsInPixels.right(), boundsInPixels.bottom() ); - - const QgsRectangle boundsMapUnits = QgsRectangle( topLeft.x(), bottomLeft.y(), bottomRight.x(), topRight.y() ); - QgsRectangle textRect = context.coordinateTransform().transformBoundingBox( boundsMapUnits, Qgis::TransformDirection::Reverse ); - return textRect; - } - - case Qgis::AnnotationPlacementMode::RelativeToMapFrame: - { - const double widthPixels = context.convertToPainterUnits( mFixedSize.width(), mFixedSizeUnit ); - const double heightPixels = context.convertToPainterUnits( mFixedSize.height(), mFixedSizeUnit ); - - QRectF boundsInPixels; - - const double centerMapX = context.mapExtent().xMinimum() + mBounds.center().x() * context.mapExtent().width(); - const double centerMapY = context.mapExtent().yMaximum() - mBounds.center().y() * context.mapExtent().height(); - QPointF center( centerMapX, centerMapY ); - if ( context.coordinateTransform().isValid() ) - { - double x = centerMapX; - double y = centerMapY; - 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() ); - const QgsPointXY bottomRight = context.mapToPixel().toMapCoordinates( boundsInPixels.right(), boundsInPixels.bottom() ); - - const QgsRectangle boundsMapUnits = QgsRectangle( topLeft.x(), bottomLeft.y(), bottomRight.x(), topRight.y() ); - QgsRectangle textRect = context.coordinateTransform().transformBoundingBox( boundsMapUnits, Qgis::TransformDirection::Reverse ); - return textRect; - } - } - BUILTIN_UNREACHABLE -} - -void QgsAnnotationPictureItem::setBounds( const QgsRectangle &bounds ) -{ - mBounds = bounds; -} - void QgsAnnotationPictureItem::setPath( Qgis::PictureFormat format, const QString &path ) { mPath = path; @@ -830,53 +160,3 @@ void QgsAnnotationPictureItem::setLockAspectRatio( bool locked ) { mLockAspectRatio = locked; } - -const QgsFillSymbol *QgsAnnotationPictureItem::backgroundSymbol() const -{ - return mBackgroundSymbol.get(); -} - -void QgsAnnotationPictureItem::setBackgroundSymbol( QgsFillSymbol *symbol ) -{ - mBackgroundSymbol.reset( symbol ); -} - -const QgsFillSymbol *QgsAnnotationPictureItem::frameSymbol() const -{ - return mFrameSymbol.get(); -} - -void QgsAnnotationPictureItem::setFrameSymbol( QgsFillSymbol *symbol ) -{ - mFrameSymbol.reset( symbol ); -} - -QSizeF QgsAnnotationPictureItem::fixedSize() const -{ - return mFixedSize; -} - -void QgsAnnotationPictureItem::setFixedSize( const QSizeF &size ) -{ - mFixedSize = size; -} - -Qgis::RenderUnit QgsAnnotationPictureItem::fixedSizeUnit() const -{ - return mFixedSizeUnit; -} - -void QgsAnnotationPictureItem::setFixedSizeUnit( Qgis::RenderUnit unit ) -{ - mFixedSizeUnit = unit; -} - -Qgis::AnnotationPlacementMode QgsAnnotationPictureItem::placementMode() const -{ - return mPlacementMode; -} - -void QgsAnnotationPictureItem::setPlacementMode( Qgis::AnnotationPlacementMode mode ) -{ - mPlacementMode = mode; -} diff --git a/src/core/annotations/qgsannotationpictureitem.h b/src/core/annotations/qgsannotationpictureitem.h index 6d95d219a63c..ea17d17de2b0 100644 --- a/src/core/annotations/qgsannotationpictureitem.h +++ b/src/core/annotations/qgsannotationpictureitem.h @@ -20,7 +20,7 @@ #include "qgis_core.h" #include "qgis_sip.h" -#include "qgsannotationitem.h" +#include "qgsannotationrectitem.h" /** * \ingroup core @@ -28,7 +28,7 @@ * * \since QGIS 3.40 */ -class CORE_EXPORT QgsAnnotationPictureItem : public QgsAnnotationItem +class CORE_EXPORT QgsAnnotationPictureItem : public QgsAnnotationRectItem { public: @@ -40,46 +40,14 @@ class CORE_EXPORT QgsAnnotationPictureItem : public QgsAnnotationItem ~QgsAnnotationPictureItem() override; QString type() const override; - Qgis::AnnotationItemFlags flags() const override; - void render( QgsRenderContext &context, QgsFeedback *feedback ) override; bool writeXml( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const override; - QList< QgsAnnotationItemNode > nodesV2( const QgsAnnotationItemEditContext &context ) const override; - Qgis::AnnotationItemEditOperationResult applyEditV2( QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext &context ) override; - QgsAnnotationItemEditOperationTransientResults *transientEditResultsV2( QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext &context ) override SIP_FACTORY; - - /** - * Creates a new polygon annotation item. - */ - static QgsAnnotationPictureItem *create() SIP_FACTORY; - bool readXml( const QDomElement &element, const QgsReadWriteContext &context ) override; QgsAnnotationPictureItem *clone() const override SIP_FACTORY; - QgsRectangle boundingBox() const override; - QgsRectangle boundingBox( QgsRenderContext &context ) const override; - - /** - * Returns the bounds of the picture. - * - * The coordinate reference system for the bounds will be the parent layer's QgsAnnotationLayer::crs(). - * - * When the placementMode() is Qgis::AnnotationPlacementMode::FixedSize then the picture will be placed - * at the center of the bounds. - * - * \see setBounds() - */ - QgsRectangle bounds() const { return mBounds; } /** - * Sets the \a bounds of the picture. - * - * The coordinate reference system for the bounds will be the parent layer's QgsAnnotationLayer::crs(). - * - * When the placementMode() is Qgis::AnnotationPlacementMode::FixedSize then the picture will be placed - * at the center of the bounds. - * - * \see bounds() + * Creates a new polygon annotation item. */ - void setBounds( const QgsRectangle &bounds ); + static QgsAnnotationPictureItem *create() SIP_FACTORY; /** * Returns the path of the image used to render the item. @@ -101,56 +69,6 @@ class CORE_EXPORT QgsAnnotationPictureItem : public QgsAnnotationItem */ void setPath( Qgis::PictureFormat format, const QString &path ); - /** - * Returns the placement mode for the picture. - * - * \see setPlacementMode() - */ - Qgis::AnnotationPlacementMode placementMode() const; - - /** - * Sets the placement \a mode for the picture. - * - * \see placementMode() - */ - void setPlacementMode( Qgis::AnnotationPlacementMode mode ); - - /** - * Returns the fixed size to use for the picture, when the placementMode() is Qgis::AnnotationPlacementMode::FixedSize. - * - * Units are retrieved via fixedSizeUnit() - * - * \see setFixedSize() - * \see fixedSizeUnit() - */ - QSizeF fixedSize() const; - - /** - * Sets the fixed \a size to use for the picture, when the placementMode() is Qgis::AnnotationPlacementMode::FixedSize. - * - * Units are set via setFixedSizeUnit() - * - * \see fixedSize() - * \see setFixedSizeUnit() - */ - void setFixedSize( const QSizeF &size ); - - /** - * Returns the units to use for fixed picture sizes, when the placementMode() is Qgis::AnnotationPlacementMode::FixedSize. - * - * \see setFixedSizeUnit() - * \see fixedSize() - */ - Qgis::RenderUnit fixedSizeUnit() const; - - /** - * Sets the \a unit to use for fixed picture sizes, when the placementMode() is Qgis::AnnotationPlacementMode::FixedSize. - * - * \see fixedSizeUnit() - * \see setFixedSize() - */ - void setFixedSizeUnit( Qgis::RenderUnit unit ); - /** * Returns TRUE if the aspect ratio of the picture will be retained. * @@ -165,89 +83,15 @@ class CORE_EXPORT QgsAnnotationPictureItem : public QgsAnnotationItem */ void setLockAspectRatio( bool locked ); - /** - * Returns TRUE if the item's background should be rendered. - * - * \see setBackgroundEnabled() - * \see backgroundSymbol() - */ - bool backgroundEnabled() const { return mDrawBackground; } - - /** - * Sets whether the item's background should be rendered. - * - * \see backgroundEnabled() - * \see setBackgroundSymbol() - */ - void setBackgroundEnabled( bool enabled ) { mDrawBackground = enabled; } - - /** - * Returns the symbol used to render the item's background. - * - * \see backgroundEnabled() - * \see setBackgroundSymbol() - */ - const QgsFillSymbol *backgroundSymbol() const; - - /** - * Sets the \a symbol used to render the item's background. - * - * The item takes ownership of the symbol. - * - * \see backgroundSymbol() - * \see setBackgroundEnabled() - */ - void setBackgroundSymbol( QgsFillSymbol *symbol SIP_TRANSFER ); - - /** - * Returns TRUE if the item's frame should be rendered. - * - * \see setFrameEnabled() - * \see frameSymbol() - */ - bool frameEnabled() const { return mDrawFrame; } + protected: - /** - * Sets whether the item's frame should be rendered. - * - * \see frameEnabled() - * \see setFrameSymbol() - */ - void setFrameEnabled( bool enabled ) { mDrawFrame = enabled; } - - /** - * Returns the symbol used to render the item's frame. - * - * \see frameEnabled() - * \see setFrameSymbol() - */ - const QgsFillSymbol *frameSymbol() const; - - /** - * Sets the \a symbol used to render the item's frame. - * - * The item takes ownership of the symbol. - * - * \see frameSymbol() - * \see setBackgroundEnabled() - */ - void setFrameSymbol( QgsFillSymbol *symbol SIP_TRANSFER ); + void renderInBounds( QgsRenderContext &context, const QRectF &painterBounds, QgsFeedback *feedback ) override; private: QString mPath; Qgis::PictureFormat mFormat = Qgis::PictureFormat::Unknown; - Qgis::AnnotationPlacementMode mPlacementMode = Qgis::AnnotationPlacementMode::SpatialBounds; - QgsRectangle mBounds; - - QSizeF mFixedSize; - Qgis::RenderUnit mFixedSizeUnit = Qgis::RenderUnit::Millimeters; - bool mLockAspectRatio = true; - bool mDrawBackground = false; - std::unique_ptr< QgsFillSymbol > mBackgroundSymbol; - bool mDrawFrame = false; - std::unique_ptr< QgsFillSymbol > mFrameSymbol; #ifdef SIP_RUN QgsAnnotationPictureItem( const QgsAnnotationPictureItem &other ); diff --git a/src/core/annotations/qgsannotationrectangletextitem.cpp b/src/core/annotations/qgsannotationrectangletextitem.cpp index fbb02c46d30b..c4faed5ca7c7 100644 --- a/src/core/annotations/qgsannotationrectangletextitem.cpp +++ b/src/core/annotations/qgsannotationrectangletextitem.cpp @@ -18,28 +18,18 @@ #include "qgsannotationrectangletextitem.h" #include "qgsgeometry.h" #include "qgsrendercontext.h" -#include "qgsannotationitemnode.h" -#include "qgsannotationitemeditoperation.h" -#include "qgsfillsymbol.h" #include "qgssymbollayerutils.h" -#include "qgsfillsymbollayer.h" #include "qgslinesymbollayer.h" #include "qgstextrenderer.h" #include "qgsunittypes.h" -#include "qgsapplication.h" #include "qgscalloutsregistry.h" -#include "qgspolygon.h" -#include "qgslinestring.h" QgsAnnotationRectangleTextItem::QgsAnnotationRectangleTextItem( const QString &text, const QgsRectangle &bounds ) - : QgsAnnotationItem() - , mBounds( bounds ) + : QgsAnnotationRectItem( bounds ) , mText( text ) { - mBackgroundSymbol = std::make_unique< QgsFillSymbol >( QgsSymbolLayerList { new QgsSimpleFillSymbolLayer( QColor( 255, 255, 255 ), Qt::BrushStyle::SolidPattern, QColor( 0, 0, 0 ), Qt::PenStyle::NoPen ) } ); - QgsSimpleLineSymbolLayer *borderSymbol = new QgsSimpleLineSymbolLayer( QColor( 0, 0, 0 ) ); - borderSymbol->setPenJoinStyle( Qt::MiterJoin ); - mFrameSymbol = std::make_unique< QgsFillSymbol >( QgsSymbolLayerList { borderSymbol } ); + setBackgroundEnabled( true ); + setFrameEnabled( true ); } QgsAnnotationRectangleTextItem::~QgsAnnotationRectangleTextItem() = default; @@ -49,99 +39,8 @@ QString QgsAnnotationRectangleTextItem::type() const return QStringLiteral( "recttext" ); } -void QgsAnnotationRectangleTextItem::render( QgsRenderContext &context, QgsFeedback *feedback ) +void QgsAnnotationRectangleTextItem::renderInBounds( QgsRenderContext &context, const QRectF &painterBounds, QgsFeedback * ) { - QgsRectangle bounds = mBounds; - if ( mPlacementMode != Qgis::AnnotationPlacementMode::RelativeToMapFrame && context.coordinateTransform().isValid() ) - { - try - { - bounds = context.coordinateTransform().transformBoundingBox( mBounds ); - } - catch ( QgsCsException & ) - { - return; - } - } - - QRectF painterBounds; - switch ( mPlacementMode ) - { - case Qgis::AnnotationPlacementMode::SpatialBounds: - painterBounds = context.mapToPixel().transformBounds( bounds.toRectF() ); - break; - - case Qgis::AnnotationPlacementMode::FixedSize: - { - const double widthPixels = context.convertToPainterUnits( mFixedSize.width(), mFixedSizeUnit ); - const double heightPixels = context.convertToPainterUnits( mFixedSize.height(), mFixedSizeUnit ); - - 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; - } - - case Qgis::AnnotationPlacementMode::RelativeToMapFrame: - { - const double widthPixels = context.convertToPainterUnits( mFixedSize.width(), mFixedSizeUnit ); - const double heightPixels = context.convertToPainterUnits( mFixedSize.height(), mFixedSizeUnit ); - - QPointF center = bounds.center().toQPointF(); - center.rx() *= context.outputSize().width(); - center.ry() *= context.outputSize().height(); - - painterBounds = QRectF( center.x() - widthPixels * 0.5, - center.y() - heightPixels * 0.5, - widthPixels, heightPixels ); - break; - } - } - - if ( painterBounds.width() < 1 || painterBounds.height() < 1 ) - return; - - if ( mDrawBackground && mBackgroundSymbol ) - { - mBackgroundSymbol->startRender( context ); - mBackgroundSymbol->renderPolygon( painterBounds, nullptr, nullptr, context ); - mBackgroundSymbol->stopRender( context ); - } - - if ( mPlacementMode != Qgis::AnnotationPlacementMode::RelativeToMapFrame && callout() ) - { - QgsCallout::QgsCalloutContext calloutContext; - renderCallout( context, painterBounds, 0, calloutContext, feedback ); - } - const double marginLeft = context.convertToPainterUnits( mMargins.left(), mMarginUnit ); const double marginTop = context.convertToPainterUnits( mMargins.top(), mMarginUnit ); const double marginRight = context.convertToPainterUnits( mMargins.right(), mMarginUnit ); @@ -163,13 +62,6 @@ void QgsAnnotationRectangleTextItem::render( QgsRenderContext &context, QgsFeedb QgsTextRenderer::convertQtVAlignment( mAlignment ), Qgis::TextRendererFlag::WrapLines ); context.setFlag( Qgis::RenderContextFlag::ApplyScalingWorkaroundForTextRendering, prevWorkaroundFlag ); - - if ( mDrawFrame && mFrameSymbol ) - { - mFrameSymbol->startRender( context ); - mFrameSymbol->renderPolygon( painterBounds, nullptr, nullptr, context ); - mFrameSymbol->stopRender( context ); - } } bool QgsAnnotationRectangleTextItem::writeXml( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const @@ -184,365 +76,10 @@ bool QgsAnnotationRectangleTextItem::writeXml( QDomElement &element, QDomDocumen element.setAttribute( QStringLiteral( "margins" ), mMargins.toString() ); element.setAttribute( QStringLiteral( "marginUnit" ), QgsUnitTypes::encodeUnit( mMarginUnit ) ); - element.setAttribute( QStringLiteral( "xMin" ), qgsDoubleToString( mBounds.xMinimum() ) ); - element.setAttribute( QStringLiteral( "xMax" ), qgsDoubleToString( mBounds.xMaximum() ) ); - element.setAttribute( QStringLiteral( "yMin" ), qgsDoubleToString( mBounds.yMinimum() ) ); - element.setAttribute( QStringLiteral( "yMax" ), qgsDoubleToString( mBounds.yMaximum() ) ); - - element.setAttribute( QStringLiteral( "sizeMode" ), qgsEnumValueToKey( mPlacementMode ) ); - element.setAttribute( QStringLiteral( "fixedWidth" ), qgsDoubleToString( mFixedSize.width() ) ); - element.setAttribute( QStringLiteral( "fixedHeight" ), qgsDoubleToString( mFixedSize.height() ) ); - element.setAttribute( QStringLiteral( "fixedSizeUnit" ), QgsUnitTypes::encodeUnit( mFixedSizeUnit ) ); - - element.setAttribute( QStringLiteral( "backgroundEnabled" ), mDrawBackground ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ); - if ( mBackgroundSymbol ) - { - QDomElement backgroundElement = document.createElement( QStringLiteral( "backgroundSymbol" ) ); - backgroundElement.appendChild( QgsSymbolLayerUtils::saveSymbol( QStringLiteral( "backgroundSymbol" ), mBackgroundSymbol.get(), document, context ) ); - element.appendChild( backgroundElement ); - } - - element.setAttribute( QStringLiteral( "frameEnabled" ), mDrawFrame ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ); - if ( mFrameSymbol ) - { - QDomElement frameElement = document.createElement( QStringLiteral( "frameSymbol" ) ); - frameElement.appendChild( QgsSymbolLayerUtils::saveSymbol( QStringLiteral( "frameSymbol" ), mFrameSymbol.get(), document, context ) ); - element.appendChild( frameElement ); - } - writeCommonProperties( element, document, context ); return true; } -QList QgsAnnotationRectangleTextItem::nodesV2( const QgsAnnotationItemEditContext &context ) const -{ - QList res; - switch ( mPlacementMode ) - { - case Qgis::AnnotationPlacementMode::SpatialBounds: - { - res = - { - QgsAnnotationItemNode( QgsVertexId( 0, 0, 0 ), QgsPointXY( mBounds.xMinimum(), mBounds.yMinimum() ), Qgis::AnnotationItemNodeType::VertexHandle ), - QgsAnnotationItemNode( QgsVertexId( 0, 0, 1 ), QgsPointXY( mBounds.xMaximum(), mBounds.yMinimum() ), Qgis::AnnotationItemNodeType::VertexHandle ), - QgsAnnotationItemNode( QgsVertexId( 0, 0, 2 ), QgsPointXY( mBounds.xMaximum(), mBounds.yMaximum() ), Qgis::AnnotationItemNodeType::VertexHandle ), - QgsAnnotationItemNode( QgsVertexId( 0, 0, 3 ), QgsPointXY( mBounds.xMinimum(), mBounds.yMaximum() ), Qgis::AnnotationItemNodeType::VertexHandle ), - }; - - QgsPointXY calloutNodePoint; - if ( !calloutAnchor().isEmpty() ) - { - calloutNodePoint = calloutAnchor().asPoint(); - } - else - { - calloutNodePoint = mBounds.center(); - } - res.append( QgsAnnotationItemNode( QgsVertexId( 1, 0, 0 ), calloutNodePoint, Qgis::AnnotationItemNodeType::CalloutHandle ) ); - - return res; - } - - case Qgis::AnnotationPlacementMode::FixedSize: - { - res = - { - QgsAnnotationItemNode( QgsVertexId( 0, 0, 0 ), mBounds.center(), Qgis::AnnotationItemNodeType::VertexHandle ) - }; - - QgsPointXY calloutNodePoint; - if ( !calloutAnchor().isEmpty() ) - { - calloutNodePoint = calloutAnchor().asPoint(); - } - else - { - calloutNodePoint = QgsPointXY( context.currentItemBounds().xMinimum(), context.currentItemBounds().yMinimum() ); - } - res.append( QgsAnnotationItemNode( QgsVertexId( 1, 0, 0 ), calloutNodePoint, Qgis::AnnotationItemNodeType::CalloutHandle ) ); - - return res; - } - - case Qgis::AnnotationPlacementMode::RelativeToMapFrame: - { - return - { - QgsAnnotationItemNode( QgsVertexId( 0, 0, 0 ), - context.currentItemBounds().center(), Qgis::AnnotationItemNodeType::VertexHandle ) - }; - } - } - BUILTIN_UNREACHABLE -} - -Qgis::AnnotationItemEditOperationResult QgsAnnotationRectangleTextItem::applyEditV2( QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext &context ) -{ - switch ( operation->type() ) - { - case QgsAbstractAnnotationItemEditOperation::Type::MoveNode: - { - QgsAnnotationItemEditOperationMoveNode *moveOperation = dynamic_cast< QgsAnnotationItemEditOperationMoveNode * >( operation ); - if ( moveOperation->nodeId().part == 0 ) - { - switch ( mPlacementMode ) - { - case Qgis::AnnotationPlacementMode::SpatialBounds: - { - switch ( moveOperation->nodeId().vertex ) - { - case 0: - mBounds = QgsRectangle( moveOperation->after().x(), - moveOperation->after().y(), - mBounds.xMaximum(), - mBounds.yMaximum() ); - break; - case 1: - mBounds = QgsRectangle( mBounds.xMinimum(), - moveOperation->after().y(), - moveOperation->after().x(), - mBounds.yMaximum() ); - break; - case 2: - mBounds = QgsRectangle( mBounds.xMinimum(), - mBounds.yMinimum(), - moveOperation->after().x(), - moveOperation->after().y() ); - break; - case 3: - mBounds = QgsRectangle( moveOperation->after().x(), - mBounds.yMinimum(), - mBounds.xMaximum(), - moveOperation->after().y() ); - break; - default: - break; - } - return Qgis::AnnotationItemEditOperationResult::Success; - } - - case Qgis::AnnotationPlacementMode::FixedSize: - { - mBounds = QgsRectangle::fromCenterAndSize( moveOperation->after(), - mBounds.width(), - mBounds.height() ); - return Qgis::AnnotationItemEditOperationResult::Success; - } - - case Qgis::AnnotationPlacementMode::RelativeToMapFrame: - { - const double deltaX = moveOperation->translationXPixels() / context.renderContext().outputSize().width(); - const double deltaY = moveOperation->translationYPixels() / context.renderContext().outputSize().height(); - mBounds = QgsRectangle::fromCenterAndSize( QgsPointXY( mBounds.center().x() + deltaX, mBounds.center().y() + deltaY ), - mBounds.width(), mBounds.height() ); - return Qgis::AnnotationItemEditOperationResult::Success; - } - } - } - else if ( moveOperation->nodeId().part == 1 ) - { - setCalloutAnchor( QgsGeometry::fromPoint( moveOperation->after() ) ); - if ( !callout() ) - { - setCallout( QgsApplication::calloutRegistry()->defaultCallout() ); - } - return Qgis::AnnotationItemEditOperationResult::Success; - } - break; - } - - case QgsAbstractAnnotationItemEditOperation::Type::TranslateItem: - { - QgsAnnotationItemEditOperationTranslateItem *moveOperation = qgis::down_cast< QgsAnnotationItemEditOperationTranslateItem * >( operation ); - switch ( mPlacementMode ) - { - - case Qgis::AnnotationPlacementMode::SpatialBounds: - mBounds = QgsRectangle( mBounds.xMinimum() + moveOperation->translationX(), - mBounds.yMinimum() + moveOperation->translationY(), - mBounds.xMaximum() + moveOperation->translationX(), - mBounds.yMaximum() + moveOperation->translationY() ); - break; - - case Qgis::AnnotationPlacementMode::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; - } - - case Qgis::AnnotationPlacementMode::RelativeToMapFrame: - { - const double deltaX = moveOperation->translationXPixels() / context.renderContext().outputSize().width(); - const double deltaY = moveOperation->translationYPixels() / context.renderContext().outputSize().height(); - mBounds = QgsRectangle::fromCenterAndSize( QgsPointXY( mBounds.center().x() + deltaX, mBounds.center().y() + deltaY ), - mBounds.width(), mBounds.height() ); - break; - } - } - return Qgis::AnnotationItemEditOperationResult::Success; - } - - case QgsAbstractAnnotationItemEditOperation::Type::DeleteNode: - case QgsAbstractAnnotationItemEditOperation::Type::AddNode: - break; - } - return Qgis::AnnotationItemEditOperationResult::Invalid; -} - -QgsAnnotationItemEditOperationTransientResults *QgsAnnotationRectangleTextItem::transientEditResultsV2( QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext &context ) -{ - switch ( operation->type() ) - { - case QgsAbstractAnnotationItemEditOperation::Type::MoveNode: - { - QgsAnnotationItemEditOperationMoveNode *moveOperation = dynamic_cast< QgsAnnotationItemEditOperationMoveNode * >( operation ); - if ( moveOperation->nodeId().part == 0 ) - { - switch ( mPlacementMode ) - { - case Qgis::AnnotationPlacementMode::SpatialBounds: - { - QgsRectangle modifiedBounds = mBounds; - switch ( moveOperation->nodeId().vertex ) - { - case 0: - modifiedBounds.setXMinimum( moveOperation->after().x() ); - modifiedBounds.setYMinimum( moveOperation->after().y() ); - break; - case 1: - modifiedBounds.setXMaximum( moveOperation->after().x() ); - modifiedBounds.setYMinimum( moveOperation->after().y() ); - break; - case 2: - modifiedBounds.setXMaximum( moveOperation->after().x() ); - modifiedBounds.setYMaximum( moveOperation->after().y() ); - break; - case 3: - modifiedBounds.setXMinimum( moveOperation->after().x() ); - modifiedBounds.setYMaximum( moveOperation->after().y() ); - break; - default: - break; - } - return new QgsAnnotationItemEditOperationTransientResults( QgsGeometry::fromRect( modifiedBounds ) ); - } - case Qgis::AnnotationPlacementMode::FixedSize: - { - const QgsRectangle currentBounds = context.currentItemBounds(); - const QgsRectangle newBounds = QgsRectangle::fromCenterAndSize( moveOperation->after(), currentBounds.width(), currentBounds.height() ); - return new QgsAnnotationItemEditOperationTransientResults( QgsGeometry::fromRect( newBounds ) ); - } - case Qgis::AnnotationPlacementMode::RelativeToMapFrame: - { - const QgsRectangle currentBounds = context.currentItemBounds(); - const QgsRectangle newBounds = QgsRectangle::fromCenterAndSize( currentBounds.center() + ( moveOperation->after() - moveOperation->before() ), - currentBounds.width(), currentBounds.height() ); - return new QgsAnnotationItemEditOperationTransientResults( QgsGeometry::fromRect( newBounds ) ); - } - } - } - else - { - QgsAnnotationItemEditOperationMoveNode *moveOperation = dynamic_cast< QgsAnnotationItemEditOperationMoveNode * >( operation ); - return new QgsAnnotationItemEditOperationTransientResults( QgsGeometry( moveOperation->after().clone() ) ); - } - break; - } - - case QgsAbstractAnnotationItemEditOperation::Type::TranslateItem: - { - QgsAnnotationItemEditOperationTranslateItem *moveOperation = qgis::down_cast< QgsAnnotationItemEditOperationTranslateItem * >( operation ); - switch ( mPlacementMode ) - { - case Qgis::AnnotationPlacementMode::SpatialBounds: - { - const QgsRectangle modifiedBounds( mBounds.xMinimum() + moveOperation->translationX(), - mBounds.yMinimum() + moveOperation->translationY(), - mBounds.xMaximum() + moveOperation->translationX(), - mBounds.yMaximum() + moveOperation->translationY() ); - return new QgsAnnotationItemEditOperationTransientResults( QgsGeometry::fromRect( modifiedBounds ) ); - } - - case Qgis::AnnotationPlacementMode::FixedSize: - { - 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( 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 ) ); - } - } - - case Qgis::AnnotationPlacementMode::RelativeToMapFrame: - { - const QgsRectangle currentBounds = context.currentItemBounds(); - const QgsRectangle newBounds = QgsRectangle::fromCenterAndSize( currentBounds.center() + QgsVector( moveOperation->translationX(), moveOperation->translationY() ), - currentBounds.width(), currentBounds.height() ); - return new QgsAnnotationItemEditOperationTransientResults( QgsGeometry::fromRect( newBounds ) ); - } - } - break; - } - - case QgsAbstractAnnotationItemEditOperation::Type::DeleteNode: - case QgsAbstractAnnotationItemEditOperation::Type::AddNode: - break; - } - return nullptr; -} - QgsAnnotationRectangleTextItem *QgsAnnotationRectangleTextItem::create() { return new QgsAnnotationRectangleTextItem( QString(), QgsRectangle() ); @@ -560,57 +97,21 @@ bool QgsAnnotationRectangleTextItem::readXml( const QDomElement &element, const mTextFormat.readXml( textFormatElem, context ); } - mBounds.setXMinimum( element.attribute( QStringLiteral( "xMin" ) ).toDouble() ); - mBounds.setXMaximum( element.attribute( QStringLiteral( "xMax" ) ).toDouble() ); - mBounds.setYMinimum( element.attribute( QStringLiteral( "yMin" ) ).toDouble() ); - mBounds.setYMaximum( element.attribute( QStringLiteral( "yMax" ) ).toDouble() ); - mPlacementMode = qgsEnumKeyToValue( element.attribute( QStringLiteral( "sizeMode" ) ), Qgis::AnnotationPlacementMode::SpatialBounds ); - mFixedSize = QSizeF( - element.attribute( QStringLiteral( "fixedWidth" ) ).toDouble(), - element.attribute( QStringLiteral( "fixedHeight" ) ).toDouble() - ); - mFixedSizeUnit = QgsUnitTypes::decodeRenderUnit( element.attribute( QStringLiteral( "fixedSizeUnit" ) ) ); - mMargins = QgsMargins::fromString( element.attribute( QStringLiteral( "margins" ) ) ); mMarginUnit = QgsUnitTypes::decodeRenderUnit( element.attribute( QStringLiteral( "marginUnit" ), QgsUnitTypes::encodeUnit( Qgis::RenderUnit::Millimeters ) ) ); mAlignment = static_cast< Qt::Alignment >( element.attribute( QStringLiteral( "alignment" ) ).toInt() ); - mDrawBackground = element.attribute( QStringLiteral( "backgroundEnabled" ), QStringLiteral( "1" ) ).toInt(); - const QDomElement backgroundSymbolElem = element.firstChildElement( QStringLiteral( "backgroundSymbol" ) ).firstChildElement(); - if ( !backgroundSymbolElem.isNull() ) - { - setBackgroundSymbol( QgsSymbolLayerUtils::loadSymbol< QgsFillSymbol >( backgroundSymbolElem, context ) ); - } - - mDrawFrame = element.attribute( QStringLiteral( "frameEnabled" ), QStringLiteral( "1" ) ).toInt(); - const QDomElement frameSymbolElem = element.firstChildElement( QStringLiteral( "frameSymbol" ) ).firstChildElement(); - if ( !frameSymbolElem.isNull() ) - { - setFrameSymbol( QgsSymbolLayerUtils::loadSymbol< QgsFillSymbol >( frameSymbolElem, context ) ); - } - readCommonProperties( element, context ); return true; } QgsAnnotationRectangleTextItem *QgsAnnotationRectangleTextItem::clone() const { - std::unique_ptr< QgsAnnotationRectangleTextItem > item = std::make_unique< QgsAnnotationRectangleTextItem >( mText, mBounds ); + std::unique_ptr< QgsAnnotationRectangleTextItem > item = std::make_unique< QgsAnnotationRectangleTextItem >( mText, bounds() ); item->setFormat( mTextFormat ); item->setAlignment( mAlignment ); - item->setPlacementMode( mPlacementMode ); - item->setFixedSize( mFixedSize ); - item->setFixedSizeUnit( mFixedSizeUnit ); - - item->setBackgroundEnabled( mDrawBackground ); - if ( mBackgroundSymbol ) - item->setBackgroundSymbol( mBackgroundSymbol->clone() ); - - item->setFrameEnabled( mDrawFrame ); - if ( mFrameSymbol ) - item->setFrameSymbol( mFrameSymbol->clone() ); item->setMargins( mMargins ); item->setMarginsUnit( mMarginUnit ); @@ -619,204 +120,9 @@ QgsAnnotationRectangleTextItem *QgsAnnotationRectangleTextItem::clone() const return item.release(); } -QgsRectangle QgsAnnotationRectangleTextItem::boundingBox() const -{ - QgsRectangle bounds; - switch ( mPlacementMode ) - { - case Qgis::AnnotationPlacementMode::SpatialBounds: - { - bounds = mBounds; - if ( callout() && !calloutAnchor().isEmpty() ) - { - QgsGeometry anchor = calloutAnchor(); - bounds.combineExtentWith( anchor.boundingBox() ); - } - break; - } - - case Qgis::AnnotationPlacementMode::FixedSize: - if ( callout() && !calloutAnchor().isEmpty() ) - { - bounds = calloutAnchor().boundingBox(); - } - else - { - bounds = QgsRectangle( mBounds.center(), mBounds.center() ); - } - break; - - case Qgis::AnnotationPlacementMode::RelativeToMapFrame: - bounds = mBounds; - break; - } - - return bounds; -} - -QgsRectangle QgsAnnotationRectangleTextItem::boundingBox( QgsRenderContext &context ) const -{ - switch ( mPlacementMode ) - { - case Qgis::AnnotationPlacementMode::SpatialBounds: - return QgsAnnotationRectangleTextItem::boundingBox(); - - case Qgis::AnnotationPlacementMode::FixedSize: - { - const double widthPixels = context.convertToPainterUnits( mFixedSize.width(), mFixedSizeUnit ); - const double heightPixels = context.convertToPainterUnits( mFixedSize.height(), mFixedSizeUnit ); - - 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() ); - const QgsPointXY bottomRight = context.mapToPixel().toMapCoordinates( boundsInPixels.right(), boundsInPixels.bottom() ); - - const QgsRectangle boundsMapUnits = QgsRectangle( topLeft.x(), bottomLeft.y(), bottomRight.x(), topRight.y() ); - QgsRectangle textRect = context.coordinateTransform().transformBoundingBox( boundsMapUnits, Qgis::TransformDirection::Reverse ); - return textRect; - } - - case Qgis::AnnotationPlacementMode::RelativeToMapFrame: - { - const double widthPixels = context.convertToPainterUnits( mFixedSize.width(), mFixedSizeUnit ); - const double heightPixels = context.convertToPainterUnits( mFixedSize.height(), mFixedSizeUnit ); - - QRectF boundsInPixels; - - const double centerMapX = context.mapExtent().xMinimum() + mBounds.center().x() * context.mapExtent().width(); - const double centerMapY = context.mapExtent().yMaximum() - mBounds.center().y() * context.mapExtent().height(); - QPointF center( centerMapX, centerMapY ); - if ( context.coordinateTransform().isValid() ) - { - double x = centerMapX; - double y = centerMapY; - 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() ); - const QgsPointXY bottomRight = context.mapToPixel().toMapCoordinates( boundsInPixels.right(), boundsInPixels.bottom() ); - - const QgsRectangle boundsMapUnits = QgsRectangle( topLeft.x(), bottomLeft.y(), bottomRight.x(), topRight.y() ); - QgsRectangle textRect = context.coordinateTransform().transformBoundingBox( boundsMapUnits, Qgis::TransformDirection::Reverse ); - return textRect; - } - } - BUILTIN_UNREACHABLE -} - -void QgsAnnotationRectangleTextItem::setBounds( const QgsRectangle &bounds ) -{ - mBounds = bounds; -} - -Qgis::AnnotationPlacementMode QgsAnnotationRectangleTextItem::placementMode() const -{ - return mPlacementMode; -} - -void QgsAnnotationRectangleTextItem::setPlacementMode( Qgis::AnnotationPlacementMode mode ) -{ - mPlacementMode = mode; -} - -QSizeF QgsAnnotationRectangleTextItem::fixedSize() const -{ - return mFixedSize; -} - -void QgsAnnotationRectangleTextItem::setFixedSize( const QSizeF &size ) -{ - mFixedSize = size; -} - -Qgis::RenderUnit QgsAnnotationRectangleTextItem::fixedSizeUnit() const -{ - return mFixedSizeUnit; -} - -void QgsAnnotationRectangleTextItem::setFixedSizeUnit( Qgis::RenderUnit unit ) -{ - mFixedSizeUnit = unit; -} - -const QgsFillSymbol *QgsAnnotationRectangleTextItem::backgroundSymbol() const -{ - return mBackgroundSymbol.get(); -} - -void QgsAnnotationRectangleTextItem::setBackgroundSymbol( QgsFillSymbol *symbol ) -{ - mBackgroundSymbol.reset( symbol ); -} - -const QgsFillSymbol *QgsAnnotationRectangleTextItem::frameSymbol() const -{ - return mFrameSymbol.get(); -} - -void QgsAnnotationRectangleTextItem::setFrameSymbol( QgsFillSymbol *symbol ) -{ - mFrameSymbol.reset( symbol ); -} - Qgis::AnnotationItemFlags QgsAnnotationRectangleTextItem::flags() const { - switch ( mPlacementMode ) + switch ( placementMode() ) { case Qgis::AnnotationPlacementMode::SpatialBounds: return Qgis::AnnotationItemFlag::SupportsReferenceScale diff --git a/src/core/annotations/qgsannotationrectangletextitem.h b/src/core/annotations/qgsannotationrectangletextitem.h index ceffe780f0f3..90f7a19fc0e0 100644 --- a/src/core/annotations/qgsannotationrectangletextitem.h +++ b/src/core/annotations/qgsannotationrectangletextitem.h @@ -20,7 +20,7 @@ #include "qgis_core.h" #include "qgis_sip.h" -#include "qgsannotationitem.h" +#include "qgsannotationrectitem.h" #include "qgstextformat.h" #include "qgsmargins.h" @@ -30,7 +30,7 @@ * * \since QGIS 3.40 */ -class CORE_EXPORT QgsAnnotationRectangleTextItem : public QgsAnnotationItem +class CORE_EXPORT QgsAnnotationRectangleTextItem : public QgsAnnotationRectItem { public: @@ -43,11 +43,7 @@ class CORE_EXPORT QgsAnnotationRectangleTextItem : public QgsAnnotationItem QString type() const override; Qgis::AnnotationItemFlags flags() const override; - void render( QgsRenderContext &context, QgsFeedback *feedback ) override; bool writeXml( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const override; - QList< QgsAnnotationItemNode > nodesV2( const QgsAnnotationItemEditContext &context ) const override; - Qgis::AnnotationItemEditOperationResult applyEditV2( QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext &context ) override; - QgsAnnotationItemEditOperationTransientResults *transientEditResultsV2( QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext &context ) override SIP_FACTORY; /** * Creates a new rectangle text annotation item. @@ -56,26 +52,6 @@ class CORE_EXPORT QgsAnnotationRectangleTextItem : public QgsAnnotationItem bool readXml( const QDomElement &element, const QgsReadWriteContext &context ) override; QgsAnnotationRectangleTextItem *clone() const override SIP_FACTORY; - QgsRectangle boundingBox() const override; - QgsRectangle boundingBox( QgsRenderContext &context ) const override; - - /** - * Returns the bounds of the text. - * - * The coordinate reference system for the bounds will be the parent layer's QgsAnnotationLayer::crs(). - * - * \see setBounds() - */ - QgsRectangle bounds() const { return mBounds; } - - /** - * Sets the \a bounds of the text. - * - * The coordinate reference system for the bounds will be the parent layer's QgsAnnotationLayer::crs(). - * - * \see bounds() - */ - void setBounds( const QgsRectangle &bounds ); /** * Returns the text rendered by the item. @@ -91,56 +67,6 @@ class CORE_EXPORT QgsAnnotationRectangleTextItem : public QgsAnnotationItem */ void setText( const QString &text ) { mText = text; } - /** - * Returns the placement mode for the text. - * - * \see setPlacementMode() - */ - Qgis::AnnotationPlacementMode placementMode() const; - - /** - * Sets the placement \a mode for the text. - * - * \see placementMode() - */ - void setPlacementMode( Qgis::AnnotationPlacementMode mode ); - - /** - * Returns the fixed size to use for the text, when the placementMode() is Qgis::AnnotationPlacementMode::FixedSize. - * - * Units are retrieved via fixedSizeUnit() - * - * \see setFixedSize() - * \see fixedSizeUnit() - */ - QSizeF fixedSize() const; - - /** - * Sets the fixed \a size to use for the text, when the placementMode() is Qgis::AnnotationPlacementMode::FixedSize. - * - * Units are set via setFixedSizeUnit() - * - * \see fixedSize() - * \see setFixedSizeUnit() - */ - void setFixedSize( const QSizeF &size ); - - /** - * Returns the units to use for fixed text sizes, when the placementMode() is Qgis::AnnotationPlacementMode::FixedSize. - * - * \see setFixedSizeUnit() - * \see fixedSize() - */ - Qgis::RenderUnit fixedSizeUnit() const; - - /** - * Sets the \a unit to use for fixed text sizes, when the placementMode() is Qgis::AnnotationPlacementMode::FixedSize. - * - * \see fixedSizeUnit() - * \see setFixedSize() - */ - void setFixedSizeUnit( Qgis::RenderUnit unit ); - /** * Returns the text format used to render the text. * @@ -169,74 +95,6 @@ class CORE_EXPORT QgsAnnotationRectangleTextItem : public QgsAnnotationItem */ void setAlignment( Qt::Alignment alignment ); - /** - * Returns TRUE if the item's background should be rendered. - * - * \see setBackgroundEnabled() - * \see backgroundSymbol() - */ - bool backgroundEnabled() const { return mDrawBackground; } - - /** - * Sets whether the item's background should be rendered. - * - * \see backgroundEnabled() - * \see setBackgroundSymbol() - */ - void setBackgroundEnabled( bool enabled ) { mDrawBackground = enabled; } - - /** - * Returns the symbol used to render the item's background. - * - * \see backgroundEnabled() - * \see setBackgroundSymbol() - */ - const QgsFillSymbol *backgroundSymbol() const; - - /** - * Sets the \a symbol used to render the item's background. - * - * The item takes ownership of the symbol. - * - * \see backgroundSymbol() - * \see setBackgroundEnabled() - */ - void setBackgroundSymbol( QgsFillSymbol *symbol SIP_TRANSFER ); - - /** - * Returns TRUE if the item's frame should be rendered. - * - * \see setFrameEnabled() - * \see frameSymbol() - */ - bool frameEnabled() const { return mDrawFrame; } - - /** - * Sets whether the item's frame should be rendered. - * - * \see frameEnabled() - * \see setFrameSymbol() - */ - void setFrameEnabled( bool enabled ) { mDrawFrame = enabled; } - - /** - * Returns the symbol used to render the item's frame. - * - * \see frameEnabled() - * \see setFrameSymbol() - */ - const QgsFillSymbol *frameSymbol() const; - - /** - * Sets the \a symbol used to render the item's frame. - * - * The item takes ownership of the symbol. - * - * \see frameSymbol() - * \see setBackgroundEnabled() - */ - void setFrameSymbol( QgsFillSymbol *symbol SIP_TRANSFER ); - /** * Returns the margins between the outside of the item's frame and the interior text. * @@ -273,22 +131,16 @@ class CORE_EXPORT QgsAnnotationRectangleTextItem : public QgsAnnotationItem */ Qgis::RenderUnit marginsUnit() const { return mMarginUnit; } - private: + protected: + + void renderInBounds( QgsRenderContext &context, const QRectF &painterBounds, QgsFeedback *feedback ) override; - Qgis::AnnotationPlacementMode mPlacementMode = Qgis::AnnotationPlacementMode::SpatialBounds; - QgsRectangle mBounds; - QSizeF mFixedSize; - Qgis::RenderUnit mFixedSizeUnit = Qgis::RenderUnit::Millimeters; + private: QString mText; QgsTextFormat mTextFormat; Qt::Alignment mAlignment = Qt::AlignLeft; - bool mDrawBackground = true; - std::unique_ptr< QgsFillSymbol > mBackgroundSymbol; - bool mDrawFrame = true; - std::unique_ptr< QgsFillSymbol > mFrameSymbol; - QgsMargins mMargins; Qgis::RenderUnit mMarginUnit = Qgis::RenderUnit::Millimeters; diff --git a/src/core/annotations/qgsannotationrectitem.cpp b/src/core/annotations/qgsannotationrectitem.cpp new file mode 100644 index 000000000000..43fc324ee1e7 --- /dev/null +++ b/src/core/annotations/qgsannotationrectitem.cpp @@ -0,0 +1,774 @@ +/*************************************************************************** + qgsannotationrectitem.cpp + ---------------- + begin : July 2024 + copyright : (C) 2024 by Nyall Dawson + email : nyall dot dawson at gmail dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsannotationrectitem.h" +#include "qgsapplication.h" +#include "qgsgeometry.h" +#include "qgsrendercontext.h" +#include "qgsannotationitemnode.h" +#include "qgsannotationitemeditoperation.h" +#include "qgspainting.h" +#include "qgsfillsymbol.h" +#include "qgssymbollayerutils.h" +#include "qgsfillsymbollayer.h" +#include "qgslinesymbollayer.h" +#include "qgscalloutsregistry.h" +#include "qgslinestring.h" +#include "qgspolygon.h" +#include "qgsunittypes.h" + +QgsAnnotationRectItem::QgsAnnotationRectItem( const QgsRectangle &bounds ) + : QgsAnnotationItem() + , mBounds( bounds ) +{ + mBackgroundSymbol = std::make_unique< QgsFillSymbol >( + QgsSymbolLayerList + { + new QgsSimpleFillSymbolLayer( QColor( 255, 255, 255 ), Qt::BrushStyle::SolidPattern, QColor( 0, 0, 0 ), Qt::PenStyle::NoPen ) + } + ); + QgsSimpleLineSymbolLayer *borderSymbol = new QgsSimpleLineSymbolLayer( QColor( 0, 0, 0 ) ); + borderSymbol->setPenJoinStyle( Qt::MiterJoin ); + mFrameSymbol = std::make_unique< QgsFillSymbol >( QgsSymbolLayerList{ borderSymbol } ); +} + +QgsAnnotationRectItem::~QgsAnnotationRectItem() = default; + +Qgis::AnnotationItemFlags QgsAnnotationRectItem::flags() const +{ + switch ( mPlacementMode ) + { + case Qgis::AnnotationPlacementMode::SpatialBounds: + return Qgis::AnnotationItemFlag::SupportsCallouts; + case Qgis::AnnotationPlacementMode::FixedSize: + return Qgis::AnnotationItemFlag::ScaleDependentBoundingBox + | Qgis::AnnotationItemFlag::SupportsCallouts; + case Qgis::AnnotationPlacementMode::RelativeToMapFrame: + return Qgis::AnnotationItemFlag::ScaleDependentBoundingBox; + } + BUILTIN_UNREACHABLE +} + +void QgsAnnotationRectItem::render( QgsRenderContext &context, QgsFeedback *feedback ) +{ + QgsRectangle bounds = mBounds; + if ( mPlacementMode != Qgis::AnnotationPlacementMode::RelativeToMapFrame && context.coordinateTransform().isValid() ) + { + try + { + bounds = context.coordinateTransform().transformBoundingBox( mBounds ); + } + catch ( QgsCsException & ) + { + return; + } + } + + QRectF painterBounds; + + switch ( mPlacementMode ) + { + case Qgis::AnnotationPlacementMode::SpatialBounds: + painterBounds = context.mapToPixel().transformBounds( bounds.toRectF() ); + break; + + case Qgis::AnnotationPlacementMode::FixedSize: + { + const double widthPixels = context.convertToPainterUnits( mFixedSize.width(), mFixedSizeUnit ); + const double heightPixels = context.convertToPainterUnits( mFixedSize.height(), mFixedSizeUnit ); + + 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; + } + + case Qgis::AnnotationPlacementMode::RelativeToMapFrame: + { + const double widthPixels = context.convertToPainterUnits( mFixedSize.width(), mFixedSizeUnit ); + const double heightPixels = context.convertToPainterUnits( mFixedSize.height(), mFixedSizeUnit ); + + QPointF center = bounds.center().toQPointF(); + center.rx() *= context.outputSize().width(); + center.ry() *= context.outputSize().height(); + + painterBounds = QRectF( center.x() - widthPixels * 0.5, + center.y() - heightPixels * 0.5, + widthPixels, heightPixels ); + break; + } + } + + if ( painterBounds.width() < 1 || painterBounds.height() < 1 ) + return; + + if ( mDrawBackground && mBackgroundSymbol ) + { + mBackgroundSymbol->startRender( context ); + mBackgroundSymbol->renderPolygon( painterBounds, nullptr, nullptr, context ); + mBackgroundSymbol->stopRender( context ); + } + + if ( mPlacementMode != Qgis::AnnotationPlacementMode::RelativeToMapFrame && callout() ) + { + QgsCallout::QgsCalloutContext calloutContext; + renderCallout( context, painterBounds, 0, calloutContext, feedback ); + } + + renderInBounds( context, painterBounds, feedback ); + + if ( mDrawFrame && mFrameSymbol ) + { + mFrameSymbol->startRender( context ); + mFrameSymbol->renderPolygon( painterBounds, nullptr, nullptr, context ); + mFrameSymbol->stopRender( context ); + } +} + +QList QgsAnnotationRectItem::nodesV2( const QgsAnnotationItemEditContext &context ) const +{ + QList res; + switch ( mPlacementMode ) + { + case Qgis::AnnotationPlacementMode::SpatialBounds: + { + res = + { + QgsAnnotationItemNode( QgsVertexId( 0, 0, 0 ), QgsPointXY( mBounds.xMinimum(), mBounds.yMinimum() ), Qgis::AnnotationItemNodeType::VertexHandle ), + QgsAnnotationItemNode( QgsVertexId( 0, 0, 1 ), QgsPointXY( mBounds.xMaximum(), mBounds.yMinimum() ), Qgis::AnnotationItemNodeType::VertexHandle ), + QgsAnnotationItemNode( QgsVertexId( 0, 0, 2 ), QgsPointXY( mBounds.xMaximum(), mBounds.yMaximum() ), Qgis::AnnotationItemNodeType::VertexHandle ), + QgsAnnotationItemNode( QgsVertexId( 0, 0, 3 ), QgsPointXY( mBounds.xMinimum(), mBounds.yMaximum() ), Qgis::AnnotationItemNodeType::VertexHandle ), + }; + + QgsPointXY calloutNodePoint; + if ( !calloutAnchor().isEmpty() ) + { + calloutNodePoint = calloutAnchor().asPoint(); + } + else + { + calloutNodePoint = mBounds.center(); + } + res.append( QgsAnnotationItemNode( QgsVertexId( 1, 0, 0 ), calloutNodePoint, Qgis::AnnotationItemNodeType::CalloutHandle ) ); + + return res; + } + + case Qgis::AnnotationPlacementMode::FixedSize: + { + res = + { + QgsAnnotationItemNode( QgsVertexId( 0, 0, 0 ), mBounds.center(), Qgis::AnnotationItemNodeType::VertexHandle ) + }; + + QgsPointXY calloutNodePoint; + if ( !calloutAnchor().isEmpty() ) + { + calloutNodePoint = calloutAnchor().asPoint(); + } + else + { + calloutNodePoint = QgsPointXY( context.currentItemBounds().xMinimum(), context.currentItemBounds().yMinimum() ); + } + res.append( QgsAnnotationItemNode( QgsVertexId( 1, 0, 0 ), calloutNodePoint, Qgis::AnnotationItemNodeType::CalloutHandle ) ); + + return res; + } + + case Qgis::AnnotationPlacementMode::RelativeToMapFrame: + { + return + { + QgsAnnotationItemNode( QgsVertexId( 0, 0, 0 ), + context.currentItemBounds().center(), Qgis::AnnotationItemNodeType::VertexHandle ) + }; + } + } + BUILTIN_UNREACHABLE +} + +Qgis::AnnotationItemEditOperationResult QgsAnnotationRectItem::applyEditV2( QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext &context ) +{ + switch ( operation->type() ) + { + case QgsAbstractAnnotationItemEditOperation::Type::MoveNode: + { + QgsAnnotationItemEditOperationMoveNode *moveOperation = dynamic_cast< QgsAnnotationItemEditOperationMoveNode * >( operation ); + if ( moveOperation->nodeId().part == 0 ) + { + switch ( mPlacementMode ) + { + case Qgis::AnnotationPlacementMode::SpatialBounds: + { + switch ( moveOperation->nodeId().vertex ) + { + case 0: + mBounds = QgsRectangle( moveOperation->after().x(), + moveOperation->after().y(), + mBounds.xMaximum(), + mBounds.yMaximum() ); + break; + case 1: + mBounds = QgsRectangle( mBounds.xMinimum(), + moveOperation->after().y(), + moveOperation->after().x(), + mBounds.yMaximum() ); + break; + case 2: + mBounds = QgsRectangle( mBounds.xMinimum(), + mBounds.yMinimum(), + moveOperation->after().x(), + moveOperation->after().y() ); + break; + case 3: + mBounds = QgsRectangle( moveOperation->after().x(), + mBounds.yMinimum(), + mBounds.xMaximum(), + moveOperation->after().y() ); + break; + default: + break; + } + return Qgis::AnnotationItemEditOperationResult::Success; + } + + case Qgis::AnnotationPlacementMode::FixedSize: + { + mBounds = QgsRectangle::fromCenterAndSize( moveOperation->after(), + mBounds.width(), + mBounds.height() ); + return Qgis::AnnotationItemEditOperationResult::Success; + } + + case Qgis::AnnotationPlacementMode::RelativeToMapFrame: + { + const double deltaX = moveOperation->translationXPixels() / context.renderContext().outputSize().width(); + const double deltaY = moveOperation->translationYPixels() / context.renderContext().outputSize().height(); + mBounds = QgsRectangle::fromCenterAndSize( QgsPointXY( mBounds.center().x() + deltaX, mBounds.center().y() + deltaY ), + mBounds.width(), mBounds.height() ); + return Qgis::AnnotationItemEditOperationResult::Success; + } + } + } + else if ( moveOperation->nodeId().part == 1 ) + { + setCalloutAnchor( QgsGeometry::fromPoint( moveOperation->after() ) ); + if ( !callout() ) + { + setCallout( QgsApplication::calloutRegistry()->defaultCallout() ); + } + return Qgis::AnnotationItemEditOperationResult::Success; + } + break; + } + + case QgsAbstractAnnotationItemEditOperation::Type::TranslateItem: + { + QgsAnnotationItemEditOperationTranslateItem *moveOperation = qgis::down_cast< QgsAnnotationItemEditOperationTranslateItem * >( operation ); + switch ( mPlacementMode ) + { + + case Qgis::AnnotationPlacementMode::SpatialBounds: + mBounds = QgsRectangle( mBounds.xMinimum() + moveOperation->translationX(), + mBounds.yMinimum() + moveOperation->translationY(), + mBounds.xMaximum() + moveOperation->translationX(), + mBounds.yMaximum() + moveOperation->translationY() ); + break; + + case Qgis::AnnotationPlacementMode::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; + } + + case Qgis::AnnotationPlacementMode::RelativeToMapFrame: + { + const double deltaX = moveOperation->translationXPixels() / context.renderContext().outputSize().width(); + const double deltaY = moveOperation->translationYPixels() / context.renderContext().outputSize().height(); + mBounds = QgsRectangle::fromCenterAndSize( QgsPointXY( mBounds.center().x() + deltaX, mBounds.center().y() + deltaY ), + mBounds.width(), mBounds.height() ); + break; + } + } + return Qgis::AnnotationItemEditOperationResult::Success; + } + + case QgsAbstractAnnotationItemEditOperation::Type::DeleteNode: + case QgsAbstractAnnotationItemEditOperation::Type::AddNode: + break; + } + return Qgis::AnnotationItemEditOperationResult::Invalid; +} + +QgsAnnotationItemEditOperationTransientResults *QgsAnnotationRectItem::transientEditResultsV2( QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext &context ) +{ + switch ( operation->type() ) + { + case QgsAbstractAnnotationItemEditOperation::Type::MoveNode: + { + QgsAnnotationItemEditOperationMoveNode *moveOperation = dynamic_cast< QgsAnnotationItemEditOperationMoveNode * >( operation ); + if ( moveOperation->nodeId().part == 0 ) + { + switch ( mPlacementMode ) + { + case Qgis::AnnotationPlacementMode::SpatialBounds: + { + QgsRectangle modifiedBounds = mBounds; + switch ( moveOperation->nodeId().vertex ) + { + case 0: + modifiedBounds.setXMinimum( moveOperation->after().x() ); + modifiedBounds.setYMinimum( moveOperation->after().y() ); + break; + case 1: + modifiedBounds.setXMaximum( moveOperation->after().x() ); + modifiedBounds.setYMinimum( moveOperation->after().y() ); + break; + case 2: + modifiedBounds.setXMaximum( moveOperation->after().x() ); + modifiedBounds.setYMaximum( moveOperation->after().y() ); + break; + case 3: + modifiedBounds.setXMinimum( moveOperation->after().x() ); + modifiedBounds.setYMaximum( moveOperation->after().y() ); + break; + default: + break; + } + return new QgsAnnotationItemEditOperationTransientResults( QgsGeometry::fromRect( modifiedBounds ) ); + } + case Qgis::AnnotationPlacementMode::FixedSize: + { + const QgsRectangle currentBounds = context.currentItemBounds(); + const QgsRectangle newBounds = QgsRectangle::fromCenterAndSize( moveOperation->after(), currentBounds.width(), currentBounds.height() ); + return new QgsAnnotationItemEditOperationTransientResults( QgsGeometry::fromRect( newBounds ) ); + } + case Qgis::AnnotationPlacementMode::RelativeToMapFrame: + { + const QgsRectangle currentBounds = context.currentItemBounds(); + const QgsRectangle newBounds = QgsRectangle::fromCenterAndSize( currentBounds.center() + ( moveOperation->after() - moveOperation->before() ), + currentBounds.width(), currentBounds.height() ); + return new QgsAnnotationItemEditOperationTransientResults( QgsGeometry::fromRect( newBounds ) ); + } + } + } + else + { + QgsAnnotationItemEditOperationMoveNode *moveOperation = dynamic_cast< QgsAnnotationItemEditOperationMoveNode * >( operation ); + return new QgsAnnotationItemEditOperationTransientResults( QgsGeometry( moveOperation->after().clone() ) ); + } + break; + } + + case QgsAbstractAnnotationItemEditOperation::Type::TranslateItem: + { + QgsAnnotationItemEditOperationTranslateItem *moveOperation = qgis::down_cast< QgsAnnotationItemEditOperationTranslateItem * >( operation ); + switch ( mPlacementMode ) + { + case Qgis::AnnotationPlacementMode::SpatialBounds: + { + const QgsRectangle modifiedBounds( mBounds.xMinimum() + moveOperation->translationX(), + mBounds.yMinimum() + moveOperation->translationY(), + mBounds.xMaximum() + moveOperation->translationX(), + mBounds.yMaximum() + moveOperation->translationY() ); + return new QgsAnnotationItemEditOperationTransientResults( QgsGeometry::fromRect( modifiedBounds ) ); + } + + case Qgis::AnnotationPlacementMode::FixedSize: + { + 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( 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 ) ); + } + } + + case Qgis::AnnotationPlacementMode::RelativeToMapFrame: + { + const QgsRectangle currentBounds = context.currentItemBounds(); + const QgsRectangle newBounds = QgsRectangle::fromCenterAndSize( currentBounds.center() + QgsVector( moveOperation->translationX(), moveOperation->translationY() ), + currentBounds.width(), currentBounds.height() ); + return new QgsAnnotationItemEditOperationTransientResults( QgsGeometry::fromRect( newBounds ) ); + } + } + break; + } + + case QgsAbstractAnnotationItemEditOperation::Type::DeleteNode: + case QgsAbstractAnnotationItemEditOperation::Type::AddNode: + break; + } + return nullptr; +} + +QgsRectangle QgsAnnotationRectItem::boundingBox() const +{ + QgsRectangle bounds; + switch ( mPlacementMode ) + { + case Qgis::AnnotationPlacementMode::SpatialBounds: + { + bounds = mBounds; + if ( callout() && !calloutAnchor().isEmpty() ) + { + QgsGeometry anchor = calloutAnchor(); + bounds.combineExtentWith( anchor.boundingBox() ); + } + break; + } + + case Qgis::AnnotationPlacementMode::FixedSize: + if ( callout() && !calloutAnchor().isEmpty() ) + { + bounds = calloutAnchor().boundingBox(); + } + else + { + bounds = QgsRectangle( mBounds.center(), mBounds.center() ); + } + break; + + case Qgis::AnnotationPlacementMode::RelativeToMapFrame: + bounds = mBounds; + break; + } + + return bounds; +} + +QgsRectangle QgsAnnotationRectItem::boundingBox( QgsRenderContext &context ) const +{ + switch ( mPlacementMode ) + { + case Qgis::AnnotationPlacementMode::SpatialBounds: + return QgsAnnotationRectItem::boundingBox(); + + case Qgis::AnnotationPlacementMode::FixedSize: + { + const double widthPixels = context.convertToPainterUnits( mFixedSize.width(), mFixedSizeUnit ); + const double heightPixels = context.convertToPainterUnits( mFixedSize.height(), mFixedSizeUnit ); + + 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() ); + const QgsPointXY bottomRight = context.mapToPixel().toMapCoordinates( boundsInPixels.right(), boundsInPixels.bottom() ); + + const QgsRectangle boundsMapUnits = QgsRectangle( topLeft.x(), bottomLeft.y(), bottomRight.x(), topRight.y() ); + QgsRectangle textRect = context.coordinateTransform().transformBoundingBox( boundsMapUnits, Qgis::TransformDirection::Reverse ); + return textRect; + } + + case Qgis::AnnotationPlacementMode::RelativeToMapFrame: + { + const double widthPixels = context.convertToPainterUnits( mFixedSize.width(), mFixedSizeUnit ); + const double heightPixels = context.convertToPainterUnits( mFixedSize.height(), mFixedSizeUnit ); + + QRectF boundsInPixels; + + const double centerMapX = context.mapExtent().xMinimum() + mBounds.center().x() * context.mapExtent().width(); + const double centerMapY = context.mapExtent().yMaximum() - mBounds.center().y() * context.mapExtent().height(); + QPointF center( centerMapX, centerMapY ); + if ( context.coordinateTransform().isValid() ) + { + double x = centerMapX; + double y = centerMapY; + 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() ); + const QgsPointXY bottomRight = context.mapToPixel().toMapCoordinates( boundsInPixels.right(), boundsInPixels.bottom() ); + + const QgsRectangle boundsMapUnits = QgsRectangle( topLeft.x(), bottomLeft.y(), bottomRight.x(), topRight.y() ); + QgsRectangle textRect = context.coordinateTransform().transformBoundingBox( boundsMapUnits, Qgis::TransformDirection::Reverse ); + return textRect; + } + } + BUILTIN_UNREACHABLE +} + +void QgsAnnotationRectItem::setBounds( const QgsRectangle &bounds ) +{ + mBounds = bounds; +} + +const QgsFillSymbol *QgsAnnotationRectItem::backgroundSymbol() const +{ + return mBackgroundSymbol.get(); +} + +void QgsAnnotationRectItem::setBackgroundSymbol( QgsFillSymbol *symbol ) +{ + mBackgroundSymbol.reset( symbol ); +} + +const QgsFillSymbol *QgsAnnotationRectItem::frameSymbol() const +{ + return mFrameSymbol.get(); +} + +void QgsAnnotationRectItem::setFrameSymbol( QgsFillSymbol *symbol ) +{ + mFrameSymbol.reset( symbol ); +} + +void QgsAnnotationRectItem::copyCommonProperties( const QgsAnnotationItem *other ) +{ + if ( const QgsAnnotationRectItem *otherRect = dynamic_cast< const QgsAnnotationRectItem * >( other ) ) + { + setPlacementMode( otherRect->mPlacementMode ); + setFixedSize( otherRect->mFixedSize ); + setFixedSizeUnit( otherRect->mFixedSizeUnit ); + + setBackgroundEnabled( otherRect->mDrawBackground ); + if ( otherRect->mBackgroundSymbol ) + setBackgroundSymbol( otherRect->mBackgroundSymbol->clone() ); + + setFrameEnabled( otherRect->mDrawFrame ); + if ( otherRect->mFrameSymbol ) + setFrameSymbol( otherRect->mFrameSymbol->clone() ); + } + + QgsAnnotationItem::copyCommonProperties( other ); +} + +bool QgsAnnotationRectItem::writeCommonProperties( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const +{ + element.setAttribute( QStringLiteral( "xMin" ), qgsDoubleToString( mBounds.xMinimum() ) ); + element.setAttribute( QStringLiteral( "xMax" ), qgsDoubleToString( mBounds.xMaximum() ) ); + element.setAttribute( QStringLiteral( "yMin" ), qgsDoubleToString( mBounds.yMinimum() ) ); + element.setAttribute( QStringLiteral( "yMax" ), qgsDoubleToString( mBounds.yMaximum() ) ); + element.setAttribute( QStringLiteral( "sizeMode" ), qgsEnumValueToKey( mPlacementMode ) ); + element.setAttribute( QStringLiteral( "fixedWidth" ), qgsDoubleToString( mFixedSize.width() ) ); + element.setAttribute( QStringLiteral( "fixedHeight" ), qgsDoubleToString( mFixedSize.height() ) ); + element.setAttribute( QStringLiteral( "fixedSizeUnit" ), QgsUnitTypes::encodeUnit( mFixedSizeUnit ) ); + + element.setAttribute( QStringLiteral( "backgroundEnabled" ), mDrawBackground ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ); + if ( mBackgroundSymbol ) + { + QDomElement backgroundElement = document.createElement( QStringLiteral( "backgroundSymbol" ) ); + backgroundElement.appendChild( QgsSymbolLayerUtils::saveSymbol( QStringLiteral( "backgroundSymbol" ), mBackgroundSymbol.get(), document, context ) ); + element.appendChild( backgroundElement ); + } + + element.setAttribute( QStringLiteral( "frameEnabled" ), mDrawFrame ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ); + if ( mFrameSymbol ) + { + QDomElement frameElement = document.createElement( QStringLiteral( "frameSymbol" ) ); + frameElement.appendChild( QgsSymbolLayerUtils::saveSymbol( QStringLiteral( "frameSymbol" ), mFrameSymbol.get(), document, context ) ); + element.appendChild( frameElement ); + } + + return QgsAnnotationItem::writeCommonProperties( element, document, context ); +} + +bool QgsAnnotationRectItem::readCommonProperties( const QDomElement &element, const QgsReadWriteContext &context ) +{ + mBounds.setXMinimum( element.attribute( QStringLiteral( "xMin" ) ).toDouble() ); + mBounds.setXMaximum( element.attribute( QStringLiteral( "xMax" ) ).toDouble() ); + mBounds.setYMinimum( element.attribute( QStringLiteral( "yMin" ) ).toDouble() ); + mBounds.setYMaximum( element.attribute( QStringLiteral( "yMax" ) ).toDouble() ); + + mPlacementMode = qgsEnumKeyToValue( element.attribute( QStringLiteral( "sizeMode" ) ), Qgis::AnnotationPlacementMode::SpatialBounds ); + + mFixedSize = QSizeF( + element.attribute( QStringLiteral( "fixedWidth" ) ).toDouble(), + element.attribute( QStringLiteral( "fixedHeight" ) ).toDouble() + ); + mFixedSizeUnit = QgsUnitTypes::decodeRenderUnit( element.attribute( QStringLiteral( "fixedSizeUnit" ) ) ); + + mDrawBackground = element.attribute( QStringLiteral( "backgroundEnabled" ), QStringLiteral( "0" ) ).toInt(); + const QDomElement backgroundSymbolElem = element.firstChildElement( QStringLiteral( "backgroundSymbol" ) ).firstChildElement(); + if ( !backgroundSymbolElem.isNull() ) + { + setBackgroundSymbol( QgsSymbolLayerUtils::loadSymbol< QgsFillSymbol >( backgroundSymbolElem, context ) ); + } + + mDrawFrame = element.attribute( QStringLiteral( "frameEnabled" ), QStringLiteral( "0" ) ).toInt(); + const QDomElement frameSymbolElem = element.firstChildElement( QStringLiteral( "frameSymbol" ) ).firstChildElement(); + if ( !frameSymbolElem.isNull() ) + { + setFrameSymbol( QgsSymbolLayerUtils::loadSymbol< QgsFillSymbol >( frameSymbolElem, context ) ); + } + + return QgsAnnotationItem::readCommonProperties( element, context ); +} + +QSizeF QgsAnnotationRectItem::fixedSize() const +{ + return mFixedSize; +} + +void QgsAnnotationRectItem::setFixedSize( const QSizeF &size ) +{ + mFixedSize = size; +} + +Qgis::RenderUnit QgsAnnotationRectItem::fixedSizeUnit() const +{ + return mFixedSizeUnit; +} + +void QgsAnnotationRectItem::setFixedSizeUnit( Qgis::RenderUnit unit ) +{ + mFixedSizeUnit = unit; +} + +Qgis::AnnotationPlacementMode QgsAnnotationRectItem::placementMode() const +{ + return mPlacementMode; +} + +void QgsAnnotationRectItem::setPlacementMode( Qgis::AnnotationPlacementMode mode ) +{ + mPlacementMode = mode; +} diff --git a/src/core/annotations/qgsannotationrectitem.h b/src/core/annotations/qgsannotationrectitem.h new file mode 100644 index 000000000000..b17584dca259 --- /dev/null +++ b/src/core/annotations/qgsannotationrectitem.h @@ -0,0 +1,227 @@ +/*************************************************************************** + qgsannotationrectitem.h + ---------------- + begin : July 2024 + copyright : (C) 2024 by Nyall Dawson + email : nyall dot dawson at gmail dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSANNOTATIONRECTITEM_H +#define QGSANNOTATIONRECTITEM_H + +#include "qgis_core.h" +#include "qgis_sip.h" +#include "qgsannotationitem.h" + +/** + * \ingroup core + * \brief Abstract base class for annotation items which render annotations in a rectangular shape. + * + * Subclasses should implement the pure virtual render() method which takes a painter bounds argument. + * + * \since QGIS 3.40 + */ +class CORE_EXPORT QgsAnnotationRectItem : public QgsAnnotationItem +{ + public: + + /** + * Constructor for QgsAnnotationRectItem, rendering the annotation + * within the specified \a bounds geometry. + */ + QgsAnnotationRectItem( const QgsRectangle &bounds ); + ~QgsAnnotationRectItem() override; + + Qgis::AnnotationItemFlags flags() const override; + void render( QgsRenderContext &context, QgsFeedback *feedback ) override; + QList< QgsAnnotationItemNode > nodesV2( const QgsAnnotationItemEditContext &context ) const override; + Qgis::AnnotationItemEditOperationResult applyEditV2( QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext &context ) override; + QgsAnnotationItemEditOperationTransientResults *transientEditResultsV2( QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext &context ) override SIP_FACTORY; + QgsRectangle boundingBox() const override; + QgsRectangle boundingBox( QgsRenderContext &context ) const override; + + /** + * Returns the bounds of the item. + * + * The coordinate reference system for the item will be the parent layer's QgsAnnotationLayer::crs(). + * + * When the placementMode() is Qgis::AnnotationPlacementMode::FixedSize then the item will be placed + * at the center of the bounds. + * + * \see setBounds() + */ + QgsRectangle bounds() const { return mBounds; } + + /** + * Sets the \a bounds of the item. + * + * The coordinate reference system for the bounds will be the parent layer's QgsAnnotationLayer::crs(). + * + * When the placementMode() is Qgis::AnnotationPlacementMode::FixedSize then the item will be placed + * at the center of the bounds. + * + * \see bounds() + */ + void setBounds( const QgsRectangle &bounds ); + + /** + * Returns the placement mode for the item. + * + * \see setPlacementMode() + */ + Qgis::AnnotationPlacementMode placementMode() const; + + /** + * Sets the placement \a mode for the item. + * + * \see placementMode() + */ + void setPlacementMode( Qgis::AnnotationPlacementMode mode ); + + /** + * Returns the fixed size to use for the item, when the placementMode() is Qgis::AnnotationPlacementMode::FixedSize. + * + * Units are retrieved via fixedSizeUnit() + * + * \see setFixedSize() + * \see fixedSizeUnit() + */ + QSizeF fixedSize() const; + + /** + * Sets the fixed \a size to use for the item, when the placementMode() is Qgis::AnnotationPlacementMode::FixedSize. + * + * Units are set via setFixedSizeUnit() + * + * \see fixedSize() + * \see setFixedSizeUnit() + */ + void setFixedSize( const QSizeF &size ); + + /** + * Returns the units to use for fixed item sizes, when the placementMode() is Qgis::AnnotationPlacementMode::FixedSize. + * + * \see setFixedSizeUnit() + * \see fixedSize() + */ + Qgis::RenderUnit fixedSizeUnit() const; + + /** + * Sets the \a unit to use for fixed item sizes, when the placementMode() is Qgis::AnnotationPlacementMode::FixedSize. + * + * \see fixedSizeUnit() + * \see setFixedSize() + */ + void setFixedSizeUnit( Qgis::RenderUnit unit ); + + /** + * Returns TRUE if the item's background should be rendered. + * + * \see setBackgroundEnabled() + * \see backgroundSymbol() + */ + bool backgroundEnabled() const { return mDrawBackground; } + + /** + * Sets whether the item's background should be rendered. + * + * \see backgroundEnabled() + * \see setBackgroundSymbol() + */ + void setBackgroundEnabled( bool enabled ) { mDrawBackground = enabled; } + + /** + * Returns the symbol used to render the item's background. + * + * \see backgroundEnabled() + * \see setBackgroundSymbol() + */ + const QgsFillSymbol *backgroundSymbol() const; + + /** + * Sets the \a symbol used to render the item's background. + * + * The item takes ownership of the symbol. + * + * \see backgroundSymbol() + * \see setBackgroundEnabled() + */ + void setBackgroundSymbol( QgsFillSymbol *symbol SIP_TRANSFER ); + + /** + * Returns TRUE if the item's frame should be rendered. + * + * \see setFrameEnabled() + * \see frameSymbol() + */ + bool frameEnabled() const { return mDrawFrame; } + + /** + * Sets whether the item's frame should be rendered. + * + * \see frameEnabled() + * \see setFrameSymbol() + */ + void setFrameEnabled( bool enabled ) { mDrawFrame = enabled; } + + /** + * Returns the symbol used to render the item's frame. + * + * \see frameEnabled() + * \see setFrameSymbol() + */ + const QgsFillSymbol *frameSymbol() const; + + /** + * Sets the \a symbol used to render the item's frame. + * + * The item takes ownership of the symbol. + * + * \see frameSymbol() + * \see setBackgroundEnabled() + */ + void setFrameSymbol( QgsFillSymbol *symbol SIP_TRANSFER ); + + protected: + void copyCommonProperties( const QgsAnnotationItem *other ) override; + bool writeCommonProperties( QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context ) const override; + bool readCommonProperties( const QDomElement &element, const QgsReadWriteContext &context ) override; + + /** + * Renders the item to the specified render \a context. + * + * The \a painterRect argument specifies the bounds in painter units where the rectangular + * item should be rendered within. + * + * The \a feedback argument can be used to detect render cancellations during expensive + * render operations. + */ + virtual void renderInBounds( QgsRenderContext &context, const QRectF &painterRect, QgsFeedback *feedback ) = 0; + + private: + + Qgis::AnnotationPlacementMode mPlacementMode = Qgis::AnnotationPlacementMode::SpatialBounds; + QgsRectangle mBounds; + + QSizeF mFixedSize; + Qgis::RenderUnit mFixedSizeUnit = Qgis::RenderUnit::Millimeters; + bool mDrawBackground = false; + std::unique_ptr< QgsFillSymbol > mBackgroundSymbol; + bool mDrawFrame = false; + std::unique_ptr< QgsFillSymbol > mFrameSymbol; + +#ifdef SIP_RUN + QgsAnnotationRectItem( const QgsAnnotationRectItem &other ); +#endif + +}; +#endif // QGSANNOTATIONRECTITEM_H From 208a93818f1b0a51f7bdc707c924117051117a53 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 19 Aug 2024 21:40:36 +1000 Subject: [PATCH 09/15] Expand tests --- .../python/test_qgsannotationrecttextitem.py | 283 +++++++++++++++++- 1 file changed, 274 insertions(+), 9 deletions(-) diff --git a/tests/src/python/test_qgsannotationrecttextitem.py b/tests/src/python/test_qgsannotationrecttextitem.py index 4b4ab6c76b67..565004d13872 100644 --- a/tests/src/python/test_qgsannotationrecttextitem.py +++ b/tests/src/python/test_qgsannotationrecttextitem.py @@ -8,7 +8,7 @@ (at your option) any later version. """ -from qgis.PyQt.QtCore import Qt, QSize +from qgis.PyQt.QtCore import Qt, QSize, QSizeF from qgis.PyQt.QtGui import QColor, QImage, QPainter from qgis.PyQt.QtXml import QDomDocument from qgis.core import ( @@ -74,8 +74,14 @@ def testBasic(self): item.setFormat(format) item.setMargins(QgsMargins(1, 2, 3, 4)) item.setMarginsUnit(Qgis.RenderUnit.Points) - - self.assertEqual(item.boundingBox().toString(3), '100.000,200.000 : 300.000,400.000') + item.setPlacementMode(Qgis.AnnotationPlacementMode.FixedSize) + 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.text(), 'different text') self.assertEqual(item.zIndex(), 11) self.assertTrue(item.backgroundEnabled()) @@ -84,6 +90,13 @@ def testBasic(self): self.assertEqual(item.format().size(), 37) self.assertEqual(item.margins(), QgsMargins(1, 2, 3, 4)) self.assertEqual(item.marginsUnit(), Qgis.RenderUnit.Points) + self.assertEqual(item.placementMode(), + Qgis.AnnotationPlacementMode.FixedSize) + 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( @@ -92,9 +105,9 @@ def testBasic(self): self.assertEqual(item.frameSymbol()[0].color(), QColor(100, 200, 250)) - def test_nodes(self): + def test_nodes_spatial_bounds(self): """ - Test nodes for item + Test nodes for item, spatial bounds mode """ item = QgsAnnotationRectangleTextItem('my text', QgsRectangle(10, 20, 30, 40)) # nodes shouldn't form a closed ring @@ -112,7 +125,32 @@ def test_nodes(self): Qgis.AnnotationItemNodeType.CalloutHandle) ]) - def test_transform(self): + def test_nodes_fixed_size(self): + """ + Test nodes for item, fixed size mode + """ + item = QgsAnnotationRectangleTextItem('my text', + QgsRectangle(10, 20, 30, 40)) + item.setPlacementMode(Qgis.AnnotationPlacementMode.FixedSize) + context = QgsAnnotationItemEditContext() + context.setCurrentItemBounds(QgsRectangle(10, 20, 30, 40)) + self.assertEqual(item.nodesV2(context), [ + QgsAnnotationItemNode(QgsVertexId(0, 0, 0), QgsPointXY(20, 30), Qgis.AnnotationItemNodeType.VertexHandle), + QgsAnnotationItemNode(QgsVertexId(1, 0, 0), QgsPointXY(10, 20), Qgis.AnnotationItemNodeType.CalloutHandle)]) + + def test_nodes_relative_to_map(self): + """ + Test nodes for item, relative to map mode + """ + item = QgsAnnotationRectangleTextItem('my text', + QgsRectangle(0.25, 0.75, 30, 40)) + item.setPlacementMode(Qgis.AnnotationPlacementMode.RelativeToMapFrame) + context = QgsAnnotationItemEditContext() + context.setCurrentItemBounds(QgsRectangle(10, 20, 30, 40)) + self.assertEqual(item.nodesV2(context), [ + QgsAnnotationItemNode(QgsVertexId(0, 0, 0), QgsPointXY(20, 30), Qgis.AnnotationItemNodeType.VertexHandle)]) + + def test_translate_spatial_bounds(self): item = QgsAnnotationRectangleTextItem('my text', QgsRectangle(10, 20, 30, 40)) self.assertEqual(item.bounds().toString(3), '10.000,20.000 : 30.000,40.000') @@ -122,7 +160,62 @@ def test_transform(self): Qgis.AnnotationItemEditOperationResult.Success) self.assertEqual(item.bounds().toString(3), '110.000,220.000 : 130.000,240.000') - def test_apply_move_node_edit(self): + def test_translate_fixed_size(self): + item = QgsAnnotationRectangleTextItem('my text', + QgsRectangle(10, 20, 30, 40)) + item.setPlacementMode(Qgis.AnnotationPlacementMode.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), + 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 = QgsAnnotationRectangleTextItem('my text', + QgsRectangle(10, 20, 30, 40)) + item.setCalloutAnchor(QgsGeometry.fromWkt('Point(1 3)')) + item.setCallout(QgsBalloonCallout()) + item.setPlacementMode(Qgis.AnnotationPlacementMode.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_translate_relative_to_map(self): + item = QgsAnnotationRectangleTextItem('my text', + QgsRectangle(0.2, 0.8, 30, 40)) + item.setPlacementMode(Qgis.AnnotationPlacementMode.RelativeToMapFrame) + self.assertEqual(item.bounds().toString(3), '0.200,0.800 : 30.000,40.000') + + context = QgsAnnotationItemEditContext() + render_context = QgsRenderContext() + render_context.setScaleFactor(5) + render_context.setOutputSize(QSize(1000, 600)) + context.setRenderContext(render_context) + + self.assertEqual(item.applyEditV2(QgsAnnotationItemEditOperationTranslateItem('', 100, 200, 100, 200), + context), + Qgis.AnnotationItemEditOperationResult.Success) + self.assertEqual(item.bounds().toString(3), '0.300,1.133 : 30.100,40.333') + self.assertEqual(item.offsetFromCallout(), QSizeF()) + + def test_apply_move_node_edit_spatial_bounds(self): item = QgsAnnotationRectangleTextItem('my text', QgsRectangle(10, 20, 30, 40)) self.assertEqual(item.bounds().toString(3), '10.000,20.000 : 30.000,40.000') @@ -152,6 +245,48 @@ def test_apply_move_node_edit(self): # callout should have been automatically created self.assertIsInstance(item.callout(), QgsCallout) + def test_apply_move_node_edit_fixed_size(self): + item = QgsAnnotationRectangleTextItem('my text', + QgsRectangle(10, 20, 30, 40)) + item.setPlacementMode(Qgis.AnnotationPlacementMode.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)), + context), + Qgis.AnnotationItemEditOperationResult.Success) + self.assertEqual(item.bounds().toString(3), '7.000,8.000 : 27.000,28.000') + + # move callout handle + self.assertEqual(item.applyEditV2(QgsAnnotationItemEditOperationMoveNode('', QgsVertexId(1, 0, 0), QgsPoint(14, 13), QgsPoint(1, 3)), QgsAnnotationItemEditContext()), Qgis.AnnotationItemEditOperationResult.Success) + self.assertEqual(item.bounds().toString(3), '7.000,8.000 : 27.000,28.000') + self.assertEqual(item.calloutAnchor().asWkt(), 'Point (1 3)') + # callout should have been automatically created + self.assertIsInstance(item.callout(), QgsCallout) + + def test_apply_move_node_edit_relative_to_map(self): + item = QgsAnnotationRectangleTextItem('my text', + QgsRectangle(0.2, 0.8, 30, 40)) + item.setPlacementMode(Qgis.AnnotationPlacementMode.RelativeToMapFrame) + self.assertEqual(item.bounds().toString(3), '0.200,0.800 : 30.000,40.000') + + context = QgsAnnotationItemEditContext() + render_context = QgsRenderContext() + render_context.setScaleFactor(5) + render_context.setOutputSize(QSize(2000, 600)) + context.setRenderContext(render_context) + + self.assertEqual(item.applyEditV2( + QgsAnnotationItemEditOperationMoveNode('', QgsVertexId(0, 0, 0), QgsPoint(30, 20), QgsPoint(17, 18), 100, 200), + context), + Qgis.AnnotationItemEditOperationResult.Success) + self.assertEqual(item.bounds().toString(3), '0.250,1.133 : 30.050,40.333') + def test_apply_delete_node_edit(self): item = QgsAnnotationRectangleTextItem('my text', QgsRectangle(10, 20, 30, 40)) @@ -166,7 +301,7 @@ def test_apply_add_node_edit(self): QgsAnnotationItemEditContext()), Qgis.AnnotationItemEditOperationResult.Invalid) - def test_transient_move_operation(self): + def test_transient_move_operation_spatial_bounds(self): item = QgsAnnotationRectangleTextItem('my text', QgsRectangle(10, 20, 30, 40)) self.assertEqual(item.bounds().toString(3), '10.000,20.000 : 30.000,40.000') @@ -182,7 +317,47 @@ def test_transient_move_operation(self): self.assertEqual(res.representativeGeometry().asWkt(), 'Point (1 3)') - def test_transient_translate_operation(self): + def test_transient_move_operation_fixed_size(self): + item = QgsAnnotationRectangleTextItem('my text', + QgsRectangle(10, 20, 30, 40)) + item.setPlacementMode(Qgis.AnnotationPlacementMode.FixedSize) + item.setFixedSize(QSizeF(56, + 57)) + item.setFixedSizeUnit(Qgis.RenderUnit.Inches) + self.assertEqual(item.bounds().toString(3), '10.000,20.000 : 30.000,40.000') + + op = QgsAnnotationItemEditOperationMoveNode('', QgsVertexId(0, 0, 1), QgsPoint(30, 20), QgsPoint(17, 18)) + context = QgsAnnotationItemEditContext() + context.setCurrentItemBounds(QgsRectangle(1, 2, 3, 4)) + res = item.transientEditResultsV2(op, context) + self.assertEqual(res.representativeGeometry().asWkt(), 'Polygon ((16 17, 18 17, 18 19, 16 19, 16 17))') + + # move callout handle + res = item.transientEditResultsV2(QgsAnnotationItemEditOperationMoveNode('', QgsVertexId(1, 0, 0), QgsPoint(14, 13), QgsPoint(1, 3)), QgsAnnotationItemEditContext()) + self.assertEqual(res.representativeGeometry().asWkt(), + 'Point (1 3)') + + def test_transient_move_operation_relative_map(self): + item = QgsAnnotationRectangleTextItem('my text', + QgsRectangle(0.2, 0.8, 30, 40)) + item.setPlacementMode(Qgis.AnnotationPlacementMode.RelativeToMapFrame) + item.setFixedSize(QSizeF(56, + 57)) + item.setFixedSizeUnit(Qgis.RenderUnit.Inches) + self.assertEqual(item.bounds().toString(3), '0.200,0.800 : 30.000,40.000') + + op = QgsAnnotationItemEditOperationMoveNode('', QgsVertexId(0, 0, 1), QgsPoint(30, 20), QgsPoint(17, 18), 100, 200) + render_context = QgsRenderContext() + render_context.setScaleFactor(5) + render_context.setOutputSize(QSize(2000, 600)) + + context = QgsAnnotationItemEditContext() + context.setRenderContext(render_context) + context.setCurrentItemBounds(QgsRectangle(1, 2, 3, 4)) + res = item.transientEditResultsV2(op, context) + self.assertEqual(res.representativeGeometry().asWkt(), 'Polygon ((-12 0, -10 0, -10 2, -12 2, -12 0))') + + def test_transient_translate_operation_spatial_bounds(self): item = QgsAnnotationRectangleTextItem('my text', QgsRectangle(10, 20, 30, 40)) self.assertEqual(item.bounds().toString(3), '10.000,20.000 : 30.000,40.000') @@ -193,6 +368,68 @@ def test_transient_translate_operation(self): self.assertEqual(res.representativeGeometry().asWkt(), 'Polygon ((110 220, 130 220, 130 240, 110 240, 110 220))') + def test_transient_translate_operation_fixed_size(self): + item = QgsAnnotationRectangleTextItem('my text', + QgsRectangle(10, 20, 30, 40)) + item.setPlacementMode(Qgis.AnnotationPlacementMode.FixedSize) + item.setFixedSize(QSizeF(56, + 57)) + item.setFixedSizeUnit(Qgis.RenderUnit.Inches) + self.assertEqual(item.bounds().toString(3), '10.000,20.000 : 30.000,40.000') + + 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 = QgsAnnotationRectangleTextItem('my text', + QgsRectangle(10, 20, 30, 40)) + item.setPlacementMode(Qgis.AnnotationPlacementMode.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 test_transient_translate_operation_relative_map(self): + item = QgsAnnotationRectangleTextItem('my text', + QgsRectangle(0.2, 0.8, 30, 40)) + item.setPlacementMode(Qgis.AnnotationPlacementMode.RelativeToMapFrame) + item.setFixedSize(QSizeF(56, + 57)) + item.setFixedSizeUnit(Qgis.RenderUnit.Inches) + self.assertEqual(item.bounds().toString(3), '0.200,0.800 : 30.000,40.000') + + op = QgsAnnotationItemEditOperationTranslateItem('', 100, 200, 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 ((101 202, 103 202, 103 204, 101 204, 101 202))') + def testReadWriteXml(self): doc = QDomDocument("testdoc") elem = doc.createElement('test') @@ -212,6 +449,14 @@ def testReadWriteXml(self): item.setMarginsUnit(Qgis.RenderUnit.Points) item.setCalloutAnchor(QgsGeometry.fromWkt('Point(1 3)')) item.setCallout(QgsBalloonCallout()) + item.setPlacementMode(Qgis.AnnotationPlacementMode.FixedSize) + item.setFixedSize(QSizeF(56, + 57)) + 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())) @@ -230,8 +475,15 @@ def testReadWriteXml(self): self.assertEqual(s2.format().size(), 37) self.assertEqual(s2.margins(), QgsMargins(1, 2, 3, 4)) self.assertEqual(s2.marginsUnit(), Qgis.RenderUnit.Points) + self.assertEqual(s2.placementMode(), + Qgis.AnnotationPlacementMode.FixedSize) + self.assertEqual(s2.fixedSize(), QSizeF(56, + 57)) + 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 = QgsAnnotationRectangleTextItem('my text', QgsRectangle(10, 20, 30, 40)) @@ -247,8 +499,14 @@ def testClone(self): item.setFormat(format) item.setMargins(QgsMargins(1, 2, 3, 4)) item.setMarginsUnit(Qgis.RenderUnit.Points) + item.setPlacementMode(Qgis.AnnotationPlacementMode.FixedSize) + item.setFixedSize(QSizeF(56, + 57)) + 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') @@ -263,8 +521,15 @@ def testClone(self): self.assertEqual(s2.format().size(), 37) self.assertEqual(s2.margins(), QgsMargins(1, 2, 3, 4)) self.assertEqual(s2.marginsUnit(), Qgis.RenderUnit.Points) + self.assertEqual(s2.placementMode(), + Qgis.AnnotationPlacementMode.FixedSize) + self.assertEqual(s2.fixedSize(), QSizeF(56, + 57)) + 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 testRender(self): item = QgsAnnotationRectangleTextItem('my text', QgsRectangle(12, 13, 14, 15)) From 305990c88bb446db0b24c65c2e8b29f84e940427 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 19 Aug 2024 21:56:36 +1000 Subject: [PATCH 10/15] Adapt rect text widget to expose placement modes --- .../qgsannotationitemwidget_impl.cpp | 79 +++++ .../qgsannotationitemwidget_impl.h | 5 + .../qgsannotationrectangulartextwidgetbase.ui | 284 ++++++++++++------ 3 files changed, 271 insertions(+), 97 deletions(-) diff --git a/src/gui/annotations/qgsannotationitemwidget_impl.cpp b/src/gui/annotations/qgsannotationitemwidget_impl.cpp index ce65fd8b8f41..0644de1d1006 100644 --- a/src/gui/annotations/qgsannotationitemwidget_impl.cpp +++ b/src/gui/annotations/qgsannotationitemwidget_impl.cpp @@ -627,6 +627,14 @@ QgsAnnotationRectangleTextItemWidget::QgsAnnotationRectangleTextItemWidget( QWid { setupUi( this ); + mSizeModeCombo->addItem( tr( "Scale Dependent Size" ), QVariant::fromValue( Qgis::AnnotationPlacementMode::SpatialBounds ) ); + mSizeModeCombo->addItem( tr( "Fixed Size" ), QVariant::fromValue( Qgis::AnnotationPlacementMode::FixedSize ) ); + mSizeModeCombo->addItem( tr( "Relative to Map" ), QVariant::fromValue( Qgis::AnnotationPlacementMode::RelativeToMapFrame ) ); + + mSizeUnitWidget->setUnits( QgsUnitTypes::RenderUnitList() << Qgis::RenderUnit::Pixels << Qgis::RenderUnit::Millimeters + << Qgis::RenderUnit::Points << Qgis::RenderUnit::Inches << Qgis::RenderUnit::Percentage ); + + mBackgroundSymbolButton->setSymbolType( Qgis::SymbolType::Fill ); mBackgroundSymbolButton->setDialogTitle( tr( "Background" ) ); mBackgroundSymbolButton->registerExpressionContextGenerator( this ); @@ -676,6 +684,15 @@ QgsAnnotationRectangleTextItemWidget::QgsAnnotationRectangleTextItemWidget( QWid connect( mSpinLeftMargin, qOverload< double >( &QgsDoubleSpinBox::valueChanged ), this, &QgsAnnotationRectangleTextItemWidget::onWidgetChanged ); connect( mSpinBottomMargin, qOverload< double >( &QgsDoubleSpinBox::valueChanged ), this, &QgsAnnotationRectangleTextItemWidget::onWidgetChanged ); connect( mMarginUnitWidget, &QgsUnitSelectionWidget::changed, this, &QgsAnnotationRectangleTextItemWidget::onWidgetChanged ); + + connect( mSizeUnitWidget, &QgsUnitSelectionWidget::changed, this, &QgsAnnotationRectangleTextItemWidget::onWidgetChanged ); + + connect( mWidthSpinBox, qOverload< double >( &QDoubleSpinBox::valueChanged ), this, &QgsAnnotationRectangleTextItemWidget::setWidth ); + connect( mHeightSpinBox, qOverload< double >( &QDoubleSpinBox::valueChanged ), this, &QgsAnnotationRectangleTextItemWidget::setHeight ); + + connect( mSizeModeCombo, qOverload( &QComboBox::currentIndexChanged ), this, &QgsAnnotationRectangleTextItemWidget::sizeModeChanged ); + mWidgetFixedSize->hide(); + sizeModeChanged(); } QgsAnnotationItem *QgsAnnotationRectangleTextItemWidget::createItem() @@ -694,6 +711,11 @@ void QgsAnnotationRectangleTextItemWidget::updateItem( QgsAnnotationItem *item ) rectTextItem->setText( mTextFormatWidget->format().allowHtmlFormatting() ? mTextEdit->toHtml() : mTextEdit->toPlainText() ); rectTextItem->setAlignment( mAlignmentComboBox->currentAlignment() | mVerticalAlignmentComboBox->currentAlignment() ); + rectTextItem->setPlacementMode( mSizeModeCombo->currentData().value< Qgis::AnnotationPlacementMode >() ); + + rectTextItem->setFixedSize( QSizeF( mWidthSpinBox->value(), mHeightSpinBox->value() ) ); + rectTextItem->setFixedSizeUnit( mSizeUnitWidget->unit() ); + rectTextItem->setBackgroundEnabled( mBackgroundCheckbox->isChecked() ); rectTextItem->setFrameEnabled( mFrameCheckbox->isChecked() ); rectTextItem->setBackgroundSymbol( mBackgroundSymbolButton->clonedSymbol< QgsFillSymbol >() ); @@ -704,6 +726,13 @@ void QgsAnnotationRectangleTextItemWidget::updateItem( QgsAnnotationItem *item ) mSpinRightMargin->value(), mSpinBottomMargin->value() ) ); rectTextItem->setMarginsUnit( mMarginUnitWidget->unit() ); + + if ( mUpdateItemPosition ) + { + rectTextItem->setBounds( mItem->bounds() ); + mUpdateItemPosition = false; + } + mBlockChangedSignal = false; mPropertiesWidget->updateItem( rectTextItem ); @@ -777,6 +806,10 @@ bool QgsAnnotationRectangleTextItemWidget::setNewItem( QgsAnnotationItem *item ) mSpinRightMargin->setValue( textItem->margins().right() ); mSpinBottomMargin->setValue( textItem->margins().bottom() ); + mWidthSpinBox->setValue( textItem->fixedSize().width() ); + mHeightSpinBox->setValue( textItem->fixedSize().height() ); + mSizeModeCombo->setCurrentIndex( mSizeModeCombo->findData( QVariant::fromValue( textItem->placementMode() ) ) ); + mBlockChangedSignal = false; return true; @@ -788,6 +821,52 @@ void QgsAnnotationRectangleTextItemWidget::onWidgetChanged() emit itemChanged(); } +void QgsAnnotationRectangleTextItemWidget::sizeModeChanged() +{ + const Qgis::AnnotationPlacementMode mode = mSizeModeCombo->currentData().value< Qgis::AnnotationPlacementMode >(); + switch ( mode ) + { + case Qgis::AnnotationPlacementMode::SpatialBounds: + mWidgetFixedSize->hide(); + break; + + case Qgis::AnnotationPlacementMode::FixedSize: + mWidgetFixedSize->show(); + break; + + case Qgis::AnnotationPlacementMode::RelativeToMapFrame: + { + if ( const QgsRenderedAnnotationItemDetails *details = renderedItemDetails() ) + { + // convert item bounds to relative position + const QgsRectangle itemBounds = details->boundingBox(); + if ( QgsMapCanvas *canvas = context().mapCanvas() ) + { + const double centerX = ( itemBounds.center().x() - canvas->extent().xMinimum() ) / canvas->extent().width(); + const double centerY = ( canvas->extent().yMaximum() - itemBounds.center().y() ) / canvas->extent().height(); + mItem->setBounds( QgsRectangle::fromCenterAndSize( QgsPointXY( centerX, centerY ), 0.5, 0.5 ) ); + mUpdateItemPosition = true; + } + } + + mWidgetFixedSize->hide(); + break; + } + } + + onWidgetChanged(); +} + +void QgsAnnotationRectangleTextItemWidget::setWidth() +{ + onWidgetChanged(); +} + +void QgsAnnotationRectangleTextItemWidget::setHeight() +{ + onWidgetChanged(); +} + void QgsAnnotationRectangleTextItemWidget::mInsertExpressionButton_clicked() { QString expression = QgsExpressionFinder::findAndSelectActiveExpression( mTextEdit->textEdit() ); diff --git a/src/gui/annotations/qgsannotationitemwidget_impl.h b/src/gui/annotations/qgsannotationitemwidget_impl.h index 2a7bfb2251d6..f35196ba660a 100644 --- a/src/gui/annotations/qgsannotationitemwidget_impl.h +++ b/src/gui/annotations/qgsannotationitemwidget_impl.h @@ -166,12 +166,17 @@ class QgsAnnotationRectangleTextItemWidget : public QgsAnnotationItemBaseWidget, private slots: void onWidgetChanged(); + void sizeModeChanged(); + void setWidth(); + void setHeight(); private: void mInsertExpressionButton_clicked(); QgsTextFormatWidget *mTextFormatWidget = nullptr; bool mBlockChangedSignal = false; + bool mUpdateItemPosition = false; + std::unique_ptr< QgsAnnotationRectangleTextItem> mItem; }; diff --git a/src/ui/annotations/qgsannotationrectangulartextwidgetbase.ui b/src/ui/annotations/qgsannotationrectangulartextwidgetbase.ui index 6f2540f4535a..aafa452027e2 100644 --- a/src/ui/annotations/qgsannotationrectangulartextwidgetbase.ui +++ b/src/ui/annotations/qgsannotationrectangulartextwidgetbase.ui @@ -17,40 +17,23 @@ 0 - - 0 - 0 0 - - + + 0 - 0 + 150 - - - - Vertical alignment - - - - - - - Horizontal alignment - - - - + Background @@ -82,77 +65,20 @@ - - - - - - - Frame - - - true - - - - - - Symbol - - - - - - - - 0 - 0 - - - - Change… - - - - - - - - - - - 0 - 0 - - - - Insert/Edit Expression… - - - QToolButton::ToolButtonPopupMode::MenuButtonPopup - - - Qt::ToolButtonStyle::ToolButtonTextOnly - - - Qt::ArrowType::DownArrow - - - - - + + 0 - 150 + 0 - - + + - + Margins @@ -314,16 +240,180 @@ - Qt::FocusPolicy::StrongFocus + Qt::StrongFocus - + + + + Vertical alignment + + + + + + + Horizontal alignment + + + + + + + + + + + Frame + + + true + + + + + + Symbol + + + + + + + + 0 + 0 + + + + Change… + + + + + + + + + + + + + Width + + + + + + + + 0 + 0 + + + + 6 + + + 100000.000000000000000 + + + 0.200000000000000 + + + 1.000000000000000 + + + false + + + + + + + Height + + + + + + + + 0 + 0 + + + + 6 + + + 100000.000000000000000 + + + 0.200000000000000 + + + 1.000000000000000 + + + false + + + + + + + Unit + + + + + + + + 0 + 0 + + + + Qt::StrongFocus + + + + + + + + + + + + + + 0 + 0 + + + + Insert/Edit Expression… + + + QToolButton::MenuButtonPopup + + + Qt::ToolButtonTextOnly + + + Qt::DownArrow + + + @@ -333,11 +423,22 @@
qgsannotationitemcommonpropertieswidget.h
1 + + QgsSymbolButton + QToolButton +
qgssymbolbutton.h
+
QgsDoubleSpinBox QDoubleSpinBox
qgsdoublespinbox.h
+ + QgsUnitSelectionWidget + QWidget +
qgsunitselectionwidget.h
+ 1 +
QgsAlignmentComboBox QComboBox @@ -349,17 +450,6 @@
qgsrichtexteditor.h
1
- - QgsSymbolButton - QToolButton -
qgssymbolbutton.h
-
- - QgsUnitSelectionWidget - QWidget -
qgsunitselectionwidget.h
- 1 -
QgsCollapsibleGroupBox QGroupBox From bf3a1d6f25bed82d7badd1a287cd7197e9103f23 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Mon, 19 Aug 2024 22:03:13 +1000 Subject: [PATCH 11/15] Expand tests --- tests/src/python/test_qgsannotation.py | 61 ++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/tests/src/python/test_qgsannotation.py b/tests/src/python/test_qgsannotation.py index a10ae6274ee3..bf00fba8f81c 100644 --- a/tests/src/python/test_qgsannotation.py +++ b/tests/src/python/test_qgsannotation.py @@ -35,6 +35,7 @@ QgsTextAnnotation, QgsVectorLayer, QgsAnnotationPictureItem, + QgsAnnotationRectangleTextItem, QgsBalloonCallout ) from qgis.gui import QgsFormAnnotation @@ -149,6 +150,66 @@ def test_svg_annotation_project_upgrade(self): self.assertAlmostEqual(item_b.bounds().center().x(), 0.2, 3) self.assertAlmostEqual(item_b.bounds().center().y(), 0.7, 3) + def test_text_annotation_project_upgrade(self): + """ + Test that text annotations are upgraded to annotation layers when loading projects + """ + 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.setHasFixedMapPosition(True) + a.setMapPosition(QgsPointXY(QPointF(20, 30))) + a.setMapPositionCrs(QgsCoordinateReferenceSystem('EPSG:4326')) + a.setFrameOffsetFromReferencePointMm( + QPointF(40 / 3.7795275, 50 / 3.7795275)) + a.document().setHtml('

test annotation

') + + b = QgsTextAnnotation() + b.fillSymbol().symbolLayer(0).setStrokeColor(QColor(0, 0, 0)) + b.markerSymbol().symbolLayer(0).setStrokeColor(QColor(0, 0, 0)) + b.setFrameSizeMm(QSizeF(300 / 3.7795275, 200 / 3.7795275)) + b.setRelativePosition(QPointF(0.2, 0.7)) + b.setHasFixedMapPosition(False) + b.document().setHtml('

test annotation

') + + p = QgsProject() + p.annotationManager().addAnnotation(a) + p.annotationManager().addAnnotation(b) + + p2 = QgsProject() + with tempfile.TemporaryDirectory() as temp_dir: + path = os.path.join(temp_dir, 'test_project.qgs') + p.write(path) + p2.read(path) + + # should be no annotations in upgraded project + self.assertFalse(p2.annotationManager().annotations()) + # annotation layer should contain picture items + items = p2.mainAnnotationLayer().items() + self.assertEqual(len(items), 2) + + item_a = [i for _, i in items.items() if not i.calloutAnchor().isEmpty()][0] + item_b = [i for _, i in items.items() if i.calloutAnchor().isEmpty()][0] + self.assertIsInstance(item_a, QgsAnnotationRectangleTextItem) + self.assertIsInstance(item_b, QgsAnnotationRectangleTextItem) + + self.assertEqual(item_a.calloutAnchor().asWkt(), 'Point (20 30)') + self.assertEqual(item_a.placementMode(), Qgis.AnnotationPlacementMode.FixedSize) + self.assertIsInstance(item_a.callout(), + QgsBalloonCallout) + self.assertAlmostEqual(item_a.fixedSize().width(), 79.375, 1) + self.assertAlmostEqual(item_a.fixedSize().height(), 52.9166, 1) + self.assertAlmostEqual(item_a.offsetFromCallout().width(), 10.5833, 1) + self.assertAlmostEqual(item_a.offsetFromCallout().height(), 13.229, 1) + + self.assertIsNone(item_b.callout()) + self.assertEqual(item_b.placementMode(), Qgis.AnnotationPlacementMode.RelativeToMapFrame) + self.assertAlmostEqual(item_b.fixedSize().width(), 79.375, 1) + self.assertAlmostEqual(item_b.fixedSize().height(), 52.9166, 1) + self.assertAlmostEqual(item_b.bounds().center().x(), 0.2, 3) + self.assertAlmostEqual(item_b.bounds().center().y(), 0.7, 3) + def testSvgAnnotation(self): """ test rendering a svg annotation""" a = QgsSvgAnnotation() From 38e1d12d5a63b4a58c257a5aeaec190a04c4386c Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 20 Aug 2024 10:04:07 +1000 Subject: [PATCH 12/15] Only upgrade annotations on desktop --- python/PyQt6/core/auto_additions/qgis.py | 5 ++++- python/PyQt6/core/auto_generated/qgis.sip.in | 1 + python/core/auto_additions/qgis.py | 5 ++++- python/core/auto_generated/qgis.sip.in | 1 + src/core/project/qgsproject.cpp | 9 ++++++++- src/core/qgis.h | 1 + src/server/qgis_mapserver.cpp | 3 ++- src/server/qgsconfigcache.cpp | 3 ++- 8 files changed, 23 insertions(+), 5 deletions(-) diff --git a/python/PyQt6/core/auto_additions/qgis.py b/python/PyQt6/core/auto_additions/qgis.py index c8fd86a12640..f039a5737eae 100644 --- a/python/PyQt6/core/auto_additions/qgis.py +++ b/python/PyQt6/core/auto_additions/qgis.py @@ -3738,7 +3738,10 @@ QgsProject.ForceReadOnlyLayers = Qgis.ProjectReadFlag.ForceReadOnlyLayers QgsProject.ForceReadOnlyLayers.is_monkey_patched = True QgsProject.ForceReadOnlyLayers.__doc__ = "Open layers in a read-only mode. (since QGIS 3.28)" -Qgis.ProjectReadFlag.__doc__ = "Flags which control project read behavior.\n\n.. note::\n\n Prior to QGIS 3.26 this was available as :py:class:`QgsProject`.ReadFlag\n\n.. versionadded:: 3.26\n\n" + '* ``FlagDontResolveLayers``: ' + Qgis.ProjectReadFlag.DontResolveLayers.__doc__ + '\n' + '* ``FlagDontLoadLayouts``: ' + Qgis.ProjectReadFlag.DontLoadLayouts.__doc__ + '\n' + '* ``FlagTrustLayerMetadata``: ' + Qgis.ProjectReadFlag.TrustLayerMetadata.__doc__ + '\n' + '* ``FlagDontStoreOriginalStyles``: ' + Qgis.ProjectReadFlag.DontStoreOriginalStyles.__doc__ + '\n' + '* ``FlagDontLoad3DViews``: ' + Qgis.ProjectReadFlag.DontLoad3DViews.__doc__ + '\n' + '* ``DontLoadProjectStyles``: ' + Qgis.ProjectReadFlag.DontLoadProjectStyles.__doc__ + '\n' + '* ``ForceReadOnlyLayers``: ' + Qgis.ProjectReadFlag.ForceReadOnlyLayers.__doc__ +QgsProject.DontUpgradeAnnotations = Qgis.ProjectReadFlag.DontUpgradeAnnotations +QgsProject.DontUpgradeAnnotations.is_monkey_patched = True +QgsProject.DontUpgradeAnnotations.__doc__ = "Don't upgrade old annotation items to QgsAnnotationItem (since QGIS 3.40)" +Qgis.ProjectReadFlag.__doc__ = "Flags which control project read behavior.\n\n.. note::\n\n Prior to QGIS 3.26 this was available as :py:class:`QgsProject`.ReadFlag\n\n.. versionadded:: 3.26\n\n" + '* ``FlagDontResolveLayers``: ' + Qgis.ProjectReadFlag.DontResolveLayers.__doc__ + '\n' + '* ``FlagDontLoadLayouts``: ' + Qgis.ProjectReadFlag.DontLoadLayouts.__doc__ + '\n' + '* ``FlagTrustLayerMetadata``: ' + Qgis.ProjectReadFlag.TrustLayerMetadata.__doc__ + '\n' + '* ``FlagDontStoreOriginalStyles``: ' + Qgis.ProjectReadFlag.DontStoreOriginalStyles.__doc__ + '\n' + '* ``FlagDontLoad3DViews``: ' + Qgis.ProjectReadFlag.DontLoad3DViews.__doc__ + '\n' + '* ``DontLoadProjectStyles``: ' + Qgis.ProjectReadFlag.DontLoadProjectStyles.__doc__ + '\n' + '* ``ForceReadOnlyLayers``: ' + Qgis.ProjectReadFlag.ForceReadOnlyLayers.__doc__ + '\n' + '* ``DontUpgradeAnnotations``: ' + Qgis.ProjectReadFlag.DontUpgradeAnnotations.__doc__ # -- Qgis.ProjectReadFlag.baseClass = Qgis Qgis.ProjectReadFlags = lambda flags=0: Qgis.ProjectReadFlag(flags) diff --git a/python/PyQt6/core/auto_generated/qgis.sip.in b/python/PyQt6/core/auto_generated/qgis.sip.in index 71972b53a8f3..3906b737d0cf 100644 --- a/python/PyQt6/core/auto_generated/qgis.sip.in +++ b/python/PyQt6/core/auto_generated/qgis.sip.in @@ -2164,6 +2164,7 @@ The development version DontLoad3DViews, DontLoadProjectStyles, ForceReadOnlyLayers, + DontUpgradeAnnotations, }; typedef QFlags ProjectReadFlags; diff --git a/python/core/auto_additions/qgis.py b/python/core/auto_additions/qgis.py index fa2db7012c23..20a3670393af 100644 --- a/python/core/auto_additions/qgis.py +++ b/python/core/auto_additions/qgis.py @@ -3675,7 +3675,10 @@ QgsProject.ForceReadOnlyLayers = Qgis.ProjectReadFlag.ForceReadOnlyLayers QgsProject.ForceReadOnlyLayers.is_monkey_patched = True QgsProject.ForceReadOnlyLayers.__doc__ = "Open layers in a read-only mode. (since QGIS 3.28)" -Qgis.ProjectReadFlag.__doc__ = "Flags which control project read behavior.\n\n.. note::\n\n Prior to QGIS 3.26 this was available as :py:class:`QgsProject`.ReadFlag\n\n.. versionadded:: 3.26\n\n" + '* ``FlagDontResolveLayers``: ' + Qgis.ProjectReadFlag.DontResolveLayers.__doc__ + '\n' + '* ``FlagDontLoadLayouts``: ' + Qgis.ProjectReadFlag.DontLoadLayouts.__doc__ + '\n' + '* ``FlagTrustLayerMetadata``: ' + Qgis.ProjectReadFlag.TrustLayerMetadata.__doc__ + '\n' + '* ``FlagDontStoreOriginalStyles``: ' + Qgis.ProjectReadFlag.DontStoreOriginalStyles.__doc__ + '\n' + '* ``FlagDontLoad3DViews``: ' + Qgis.ProjectReadFlag.DontLoad3DViews.__doc__ + '\n' + '* ``DontLoadProjectStyles``: ' + Qgis.ProjectReadFlag.DontLoadProjectStyles.__doc__ + '\n' + '* ``ForceReadOnlyLayers``: ' + Qgis.ProjectReadFlag.ForceReadOnlyLayers.__doc__ +QgsProject.DontUpgradeAnnotations = Qgis.ProjectReadFlag.DontUpgradeAnnotations +QgsProject.DontUpgradeAnnotations.is_monkey_patched = True +QgsProject.DontUpgradeAnnotations.__doc__ = "Don't upgrade old annotation items to QgsAnnotationItem (since QGIS 3.40)" +Qgis.ProjectReadFlag.__doc__ = "Flags which control project read behavior.\n\n.. note::\n\n Prior to QGIS 3.26 this was available as :py:class:`QgsProject`.ReadFlag\n\n.. versionadded:: 3.26\n\n" + '* ``FlagDontResolveLayers``: ' + Qgis.ProjectReadFlag.DontResolveLayers.__doc__ + '\n' + '* ``FlagDontLoadLayouts``: ' + Qgis.ProjectReadFlag.DontLoadLayouts.__doc__ + '\n' + '* ``FlagTrustLayerMetadata``: ' + Qgis.ProjectReadFlag.TrustLayerMetadata.__doc__ + '\n' + '* ``FlagDontStoreOriginalStyles``: ' + Qgis.ProjectReadFlag.DontStoreOriginalStyles.__doc__ + '\n' + '* ``FlagDontLoad3DViews``: ' + Qgis.ProjectReadFlag.DontLoad3DViews.__doc__ + '\n' + '* ``DontLoadProjectStyles``: ' + Qgis.ProjectReadFlag.DontLoadProjectStyles.__doc__ + '\n' + '* ``ForceReadOnlyLayers``: ' + Qgis.ProjectReadFlag.ForceReadOnlyLayers.__doc__ + '\n' + '* ``DontUpgradeAnnotations``: ' + Qgis.ProjectReadFlag.DontUpgradeAnnotations.__doc__ # -- Qgis.ProjectReadFlag.baseClass = Qgis QgsProject.ReadFlags = Qgis.ProjectReadFlags diff --git a/python/core/auto_generated/qgis.sip.in b/python/core/auto_generated/qgis.sip.in index 12b169a0ee4c..effc72c91ebd 100644 --- a/python/core/auto_generated/qgis.sip.in +++ b/python/core/auto_generated/qgis.sip.in @@ -2164,6 +2164,7 @@ The development version DontLoad3DViews, DontLoadProjectStyles, ForceReadOnlyLayers, + DontUpgradeAnnotations, }; typedef QFlags ProjectReadFlags; diff --git a/src/core/project/qgsproject.cpp b/src/core/project/qgsproject.cpp index 9eef91904f9a..ca7e5e15c5e6 100644 --- a/src/core/project/qgsproject.cpp +++ b/src/core/project/qgsproject.cpp @@ -2499,7 +2499,14 @@ bool QgsProject::readProjectFile( const QString &filename, Qgis::ProjectReadFlag emit labelingEngineSettingsChanged(); profile.switchTask( tr( "Loading annotations" ) ); - mAnnotationManager->readXmlAndUpgradeToAnnotationLayerItems( doc->documentElement(), context, mMainAnnotationLayer, mTransformContext ); + if ( flags & Qgis::ProjectReadFlag::DontUpgradeAnnotations ) + { + mAnnotationManager->readXml( doc->documentElement(), context ); + } + else + { + mAnnotationManager->readXmlAndUpgradeToAnnotationLayerItems( doc->documentElement(), context, mMainAnnotationLayer, mTransformContext ); + } if ( !( flags & Qgis::ProjectReadFlag::DontLoadLayouts ) ) { profile.switchTask( tr( "Loading layouts" ) ); diff --git a/src/core/qgis.h b/src/core/qgis.h index 0867c94f6b84..506b69e284c4 100644 --- a/src/core/qgis.h +++ b/src/core/qgis.h @@ -3838,6 +3838,7 @@ class CORE_EXPORT Qgis DontLoad3DViews SIP_MONKEYPATCH_COMPAT_NAME( FlagDontLoad3DViews ) = 1 << 4, //!< Skip loading 3D views (since QGIS 3.26) DontLoadProjectStyles = 1 << 5, //!< Skip loading project style databases (deprecated -- use ProjectCapability::ProjectStyles flag instead) ForceReadOnlyLayers = 1 << 6, //!< Open layers in a read-only mode. (since QGIS 3.28) + DontUpgradeAnnotations = 1 << 7, //!< Don't upgrade old annotation items to QgsAnnotationItem (since QGIS 3.40) }; Q_ENUM( ProjectReadFlag ) diff --git a/src/server/qgis_mapserver.cpp b/src/server/qgis_mapserver.cpp index 38d9fe5dcb7c..88f935853b74 100644 --- a/src/server/qgis_mapserver.cpp +++ b/src/server/qgis_mapserver.cpp @@ -612,7 +612,8 @@ int main( int argc, char *argv[] ) Qgis::ProjectReadFlag::DontResolveLayers | Qgis::ProjectReadFlag::DontLoadLayouts | Qgis::ProjectReadFlag::DontStoreOriginalStyles - | Qgis::ProjectReadFlag::DontLoad3DViews ) ) + | Qgis::ProjectReadFlag::DontLoad3DViews + | Qgis::ProjectReadFlag::DontUpgradeAnnotations ) ) { std::cout << QObject::tr( "Project file not found, the option will be ignored." ).toStdString() << std::endl; } diff --git a/src/server/qgsconfigcache.cpp b/src/server/qgsconfigcache.cpp index 6731eb6fab81..73f462a24179 100644 --- a/src/server/qgsconfigcache.cpp +++ b/src/server/qgsconfigcache.cpp @@ -109,7 +109,8 @@ const QgsProject *QgsConfigCache::project( const QString &path, const QgsServerS // Always skip original styles storage Qgis::ProjectReadFlags readFlags = Qgis::ProjectReadFlag::DontStoreOriginalStyles - | Qgis::ProjectReadFlag::DontLoad3DViews; + | Qgis::ProjectReadFlag::DontLoad3DViews + | Qgis::ProjectReadFlag::DontUpgradeAnnotations; if ( settings ) { // Activate trust layer metadata flag From aaaf32d75e0bfe90ecabaa001372deff15393fdf Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 20 Aug 2024 11:03:19 +1000 Subject: [PATCH 13/15] Use resize handles for corner nodes Fixes #58427 --- .../annotations/qgsannotationitemnode.sip.in | 20 +++++++++++++- .../annotations/qgsannotationitemnode.sip.in | 20 +++++++++++++- src/core/annotations/qgsannotationitemnode.h | 26 ++++++++++++++++++- .../annotations/qgsannotationrectitem.cpp | 8 +++--- .../qgsmaptoolmodifyannotation.cpp | 2 +- 5 files changed, 68 insertions(+), 8 deletions(-) diff --git a/python/PyQt6/core/auto_generated/annotations/qgsannotationitemnode.sip.in b/python/PyQt6/core/auto_generated/annotations/qgsannotationitemnode.sip.in index 22a40c4f5697..d2482906d2cb 100644 --- a/python/PyQt6/core/auto_generated/annotations/qgsannotationitemnode.sip.in +++ b/python/PyQt6/core/auto_generated/annotations/qgsannotationitemnode.sip.in @@ -24,7 +24,7 @@ Contains information about a node used for editing an annotation item. QgsAnnotationItemNode(); - QgsAnnotationItemNode( const QgsVertexId &id, const QgsPointXY &point, Qgis::AnnotationItemNodeType type ); + QgsAnnotationItemNode( const QgsVertexId &id, const QgsPointXY &point, Qgis::AnnotationItemNodeType type, Qt::CursorShape cursor = Qt::CursorShape::ArrowCursor ); %Docstring Constructor for QgsAnnotationItemNode, with the specified ``id``, ``point`` and ``type``. %End @@ -73,6 +73,24 @@ Returns the node type. Sets the node type. .. seealso:: :py:func:`type` +%End + + Qt::CursorShape cursor() const; +%Docstring +Returns the mouse cursor shape to use when hovering the node. + +.. seealso:: :py:func:`setCursor` + +.. versionadded:: 3.34 +%End + + void setCursor( Qt::CursorShape shape ); +%Docstring +Sets the mouse cursor ``shape`` to use when hovering the node. + +.. seealso:: :py:func:`cursor` + +.. versionadded:: 3.34 %End bool operator==( const QgsAnnotationItemNode &other ) const; diff --git a/python/core/auto_generated/annotations/qgsannotationitemnode.sip.in b/python/core/auto_generated/annotations/qgsannotationitemnode.sip.in index 22a40c4f5697..d2482906d2cb 100644 --- a/python/core/auto_generated/annotations/qgsannotationitemnode.sip.in +++ b/python/core/auto_generated/annotations/qgsannotationitemnode.sip.in @@ -24,7 +24,7 @@ Contains information about a node used for editing an annotation item. QgsAnnotationItemNode(); - QgsAnnotationItemNode( const QgsVertexId &id, const QgsPointXY &point, Qgis::AnnotationItemNodeType type ); + QgsAnnotationItemNode( const QgsVertexId &id, const QgsPointXY &point, Qgis::AnnotationItemNodeType type, Qt::CursorShape cursor = Qt::CursorShape::ArrowCursor ); %Docstring Constructor for QgsAnnotationItemNode, with the specified ``id``, ``point`` and ``type``. %End @@ -73,6 +73,24 @@ Returns the node type. Sets the node type. .. seealso:: :py:func:`type` +%End + + Qt::CursorShape cursor() const; +%Docstring +Returns the mouse cursor shape to use when hovering the node. + +.. seealso:: :py:func:`setCursor` + +.. versionadded:: 3.34 +%End + + void setCursor( Qt::CursorShape shape ); +%Docstring +Sets the mouse cursor ``shape`` to use when hovering the node. + +.. seealso:: :py:func:`cursor` + +.. versionadded:: 3.34 %End bool operator==( const QgsAnnotationItemNode &other ) const; diff --git a/src/core/annotations/qgsannotationitemnode.h b/src/core/annotations/qgsannotationitemnode.h index d2aba27390c1..51b3f10820f6 100644 --- a/src/core/annotations/qgsannotationitemnode.h +++ b/src/core/annotations/qgsannotationitemnode.h @@ -37,10 +37,11 @@ class CORE_EXPORT QgsAnnotationItemNode /** * Constructor for QgsAnnotationItemNode, with the specified \a id, \a point and \a type. */ - QgsAnnotationItemNode( const QgsVertexId &id, const QgsPointXY &point, Qgis::AnnotationItemNodeType type ) + QgsAnnotationItemNode( const QgsVertexId &id, const QgsPointXY &point, Qgis::AnnotationItemNodeType type, Qt::CursorShape cursor = Qt::CursorShape::ArrowCursor ) : mId( id ) , mPoint( point ) , mType( type ) + , mCursor( cursor ) {} #ifdef SIP_RUN @@ -91,6 +92,28 @@ class CORE_EXPORT QgsAnnotationItemNode */ void setType( Qgis::AnnotationItemNodeType type ) { mType = type; } + /** + * Returns the mouse cursor shape to use when hovering the node. + * + * \see setCursor() + * \since QGIS 3.34 + */ + Qt::CursorShape cursor() const + { + return mCursor; + } + + /** + * Sets the mouse cursor \a shape to use when hovering the node. + * + * \see cursor() + * \since QGIS 3.34 + */ + void setCursor( Qt::CursorShape shape ) + { + mCursor = shape; + } + // TODO c++20 - replace with = default bool operator==( const QgsAnnotationItemNode &other ) const { @@ -107,6 +130,7 @@ class CORE_EXPORT QgsAnnotationItemNode QgsVertexId mId; QgsPointXY mPoint; Qgis::AnnotationItemNodeType mType = Qgis::AnnotationItemNodeType::VertexHandle; + Qt::CursorShape mCursor = Qt::CursorShape::ArrowCursor; }; diff --git a/src/core/annotations/qgsannotationrectitem.cpp b/src/core/annotations/qgsannotationrectitem.cpp index 43fc324ee1e7..43c0108dd6e0 100644 --- a/src/core/annotations/qgsannotationrectitem.cpp +++ b/src/core/annotations/qgsannotationrectitem.cpp @@ -176,10 +176,10 @@ QList QgsAnnotationRectItem::nodesV2( const QgsAnnotation { res = { - QgsAnnotationItemNode( QgsVertexId( 0, 0, 0 ), QgsPointXY( mBounds.xMinimum(), mBounds.yMinimum() ), Qgis::AnnotationItemNodeType::VertexHandle ), - QgsAnnotationItemNode( QgsVertexId( 0, 0, 1 ), QgsPointXY( mBounds.xMaximum(), mBounds.yMinimum() ), Qgis::AnnotationItemNodeType::VertexHandle ), - QgsAnnotationItemNode( QgsVertexId( 0, 0, 2 ), QgsPointXY( mBounds.xMaximum(), mBounds.yMaximum() ), Qgis::AnnotationItemNodeType::VertexHandle ), - QgsAnnotationItemNode( QgsVertexId( 0, 0, 3 ), QgsPointXY( mBounds.xMinimum(), mBounds.yMaximum() ), Qgis::AnnotationItemNodeType::VertexHandle ), + QgsAnnotationItemNode( QgsVertexId( 0, 0, 0 ), QgsPointXY( mBounds.xMinimum(), mBounds.yMinimum() ), Qgis::AnnotationItemNodeType::VertexHandle, Qt::CursorShape::SizeBDiagCursor ), + QgsAnnotationItemNode( QgsVertexId( 0, 0, 1 ), QgsPointXY( mBounds.xMaximum(), mBounds.yMinimum() ), Qgis::AnnotationItemNodeType::VertexHandle, Qt::CursorShape::SizeFDiagCursor ), + QgsAnnotationItemNode( QgsVertexId( 0, 0, 2 ), QgsPointXY( mBounds.xMaximum(), mBounds.yMaximum() ), Qgis::AnnotationItemNodeType::VertexHandle, Qt::CursorShape::SizeBDiagCursor ), + QgsAnnotationItemNode( QgsVertexId( 0, 0, 3 ), QgsPointXY( mBounds.xMinimum(), mBounds.yMaximum() ), Qgis::AnnotationItemNodeType::VertexHandle, Qt::CursorShape::SizeFDiagCursor ), }; QgsPointXY calloutNodePoint; diff --git a/src/gui/annotations/qgsmaptoolmodifyannotation.cpp b/src/gui/annotations/qgsmaptoolmodifyannotation.cpp index b4e3ff4f1078..10d81bdd4460 100644 --- a/src/gui/annotations/qgsmaptoolmodifyannotation.cpp +++ b/src/gui/annotations/qgsmaptoolmodifyannotation.cpp @@ -825,7 +825,7 @@ void QgsMapToolModifyAnnotation::setHoveredItemFromPoint( const QgsPointXY &mapP mHoveredNodeRubberBand->addPoint( hoveredNode.point() ); mHoveredNodeRubberBand->show(); - setCursor( Qt::ArrowCursor ); + setCursor( hoveredNode.cursor() ); } } From ffe4099822b3eb30ac0af84def5deb8eadbc74c0 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 20 Aug 2024 15:18:29 +1000 Subject: [PATCH 14/15] Fix algorithm --- src/analysis/processing/qgsprojectstylealgorithms.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/analysis/processing/qgsprojectstylealgorithms.cpp b/src/analysis/processing/qgsprojectstylealgorithms.cpp index ed29c857c8e5..b26e3c8714dd 100644 --- a/src/analysis/processing/qgsprojectstylealgorithms.cpp +++ b/src/analysis/processing/qgsprojectstylealgorithms.cpp @@ -203,7 +203,7 @@ QVariantMap QgsStyleFromProjectAlgorithm::processAlgorithm( const QVariantMap &p { // load project from path QgsProject p( nullptr, Qgis::ProjectCapabilities() ); - if ( !p.read( mProjectPath, Qgis::ProjectReadFlag::DontResolveLayers | Qgis::ProjectReadFlag::DontLoad3DViews ) ) + if ( !p.read( mProjectPath, Qgis::ProjectReadFlag::DontResolveLayers | Qgis::ProjectReadFlag::DontLoad3DViews | Qgis::ProjectReadFlag::DontUpgradeAnnotations ) ) { throw QgsProcessingException( QObject::tr( "Could not read project %1" ).arg( mProjectPath ) ); } From ff96802e303f9677bdaecb0987183af73433ea01 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sun, 25 Aug 2024 08:45:57 +1000 Subject: [PATCH 15/15] Address review --- src/core/annotations/qgsannotationmanager.cpp | 1 - src/core/annotations/qgsannotationrectitem.cpp | 6 ++---- tests/src/python/test_qgsannotation.py | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/core/annotations/qgsannotationmanager.cpp b/src/core/annotations/qgsannotationmanager.cpp index bbeb2d0d4dd6..fa366ab9c4ae 100644 --- a/src/core/annotations/qgsannotationmanager.cpp +++ b/src/core/annotations/qgsannotationmanager.cpp @@ -206,7 +206,6 @@ std::unique_ptr QgsAnnotationManager::convertToAnnotationItem layer->setLinkedVisibilityLayer( source->mapLayer() ); return true; - }; if ( const QgsSvgAnnotation *svg = dynamic_cast< const QgsSvgAnnotation *>( annotation ) ) diff --git a/src/core/annotations/qgsannotationrectitem.cpp b/src/core/annotations/qgsannotationrectitem.cpp index 43c0108dd6e0..55a4d48f1503 100644 --- a/src/core/annotations/qgsannotationrectitem.cpp +++ b/src/core/annotations/qgsannotationrectitem.cpp @@ -35,12 +35,10 @@ QgsAnnotationRectItem::QgsAnnotationRectItem( const QgsRectangle &bounds ) : QgsAnnotationItem() , mBounds( bounds ) { - mBackgroundSymbol = std::make_unique< QgsFillSymbol >( - QgsSymbolLayerList + mBackgroundSymbol = std::make_unique< QgsFillSymbol >( QgsSymbolLayerList { new QgsSimpleFillSymbolLayer( QColor( 255, 255, 255 ), Qt::BrushStyle::SolidPattern, QColor( 0, 0, 0 ), Qt::PenStyle::NoPen ) - } - ); + } ); QgsSimpleLineSymbolLayer *borderSymbol = new QgsSimpleLineSymbolLayer( QColor( 0, 0, 0 ) ); borderSymbol->setPenJoinStyle( Qt::MiterJoin ); mFrameSymbol = std::make_unique< QgsFillSymbol >( QgsSymbolLayerList{ borderSymbol } ); diff --git a/tests/src/python/test_qgsannotation.py b/tests/src/python/test_qgsannotation.py index bf00fba8f81c..a5eee2462d70 100644 --- a/tests/src/python/test_qgsannotation.py +++ b/tests/src/python/test_qgsannotation.py @@ -185,7 +185,7 @@ def test_text_annotation_project_upgrade(self): # should be no annotations in upgraded project self.assertFalse(p2.annotationManager().annotations()) - # annotation layer should contain picture items + # annotation layer should contain text items items = p2.mainAnnotationLayer().items() self.assertEqual(len(items), 2)