diff --git a/python/core/auto_generated/layertree/qgslegendpatchshape.sip.in b/python/core/auto_generated/layertree/qgslegendpatchshape.sip.in index 029348438bf..3159ad4ea17 100644 --- a/python/core/auto_generated/layertree/qgslegendpatchshape.sip.in +++ b/python/core/auto_generated/layertree/qgslegendpatchshape.sip.in @@ -116,11 +116,6 @@ The default behavior is to respect the geometry()'s aspect ratio. Converts the patch shape to a set of QPolygonF objects representing how the patch should be drawn for a symbol of the given ``type`` at the specified ``size`` (as geometry parts and rings). -%End - - static QList< QList< QPolygonF > > defaultPatch( QgsSymbol::SymbolType type, QSizeF size ); -%Docstring -Returns the default patch geometry for the given symbol ``type`` and ``size`` as a set of QPolygonF objects (parts and rings). %End void readXml( const QDomElement &element, const QgsReadWriteContext &context ); diff --git a/python/core/auto_generated/symbology/qgsstyle.sip.in b/python/core/auto_generated/symbology/qgsstyle.sip.in index 26dea77bc1c..d88db6ff8ea 100644 --- a/python/core/auto_generated/symbology/qgsstyle.sip.in +++ b/python/core/auto_generated/symbology/qgsstyle.sip.in @@ -552,6 +552,24 @@ Removes label settings from the style. Changes a label setting's name. .. versionadded:: 3.10 +%End + + QgsLegendPatchShape defaultPatch( QgsSymbol::SymbolType type, QSizeF size ) const; +%Docstring +Returns the default legend patch shape for the given symbol ``type``. + +.. seealso:: :py:func:`defaultPatchAsQPolygonF` + +.. versionadded:: 3.14 +%End + + QList< QList< QPolygonF > > defaultPatchAsQPolygonF( QgsSymbol::SymbolType type, QSizeF size ) const; +%Docstring +Returns the default patch geometry for the given symbol ``type`` and ``size`` as a set of QPolygonF objects (parts and rings). + +.. seealso:: :py:func:`defaultPatch` + +.. versionadded:: 3.14 %End bool createDatabase( const QString &filename ); diff --git a/src/core/layertree/qgslegendpatchshape.cpp b/src/core/layertree/qgslegendpatchshape.cpp index 643b93a8cc6..80964733ffb 100644 --- a/src/core/layertree/qgslegendpatchshape.cpp +++ b/src/core/layertree/qgslegendpatchshape.cpp @@ -19,6 +19,7 @@ email : nyall dot dawson at gmail dot com #include "qgsmultilinestring.h" #include "qgslinestring.h" #include "qgspolygon.h" +#include "qgsstyle.h" QgsLegendPatchShape::QgsLegendPatchShape( QgsSymbol::SymbolType type, const QgsGeometry &geometry, bool preserveAspectRatio ) : mSymbolType( type ) @@ -83,7 +84,7 @@ QPolygonF curveToPolygonF( const QgsCurve *curve ) QList > QgsLegendPatchShape::toQPolygonF( QgsSymbol::SymbolType type, QSizeF size ) const { if ( isNull() || type != mSymbolType ) - return defaultPatch( type, size ); + return QgsStyle::defaultStyle()->defaultPatchAsQPolygonF( type, size ); // scale and translate to desired size @@ -128,7 +129,7 @@ QList > QgsLegendPatchShape::toQPolygonF( QgsSymbol::SymbolType } else { - points << QPointF( size.width() / 2, size.height() / 2 ); + points << QPointF( static_cast< int >( size.width() ) / 2, static_cast< int >( size.height() ) / 2 ); } return QList< QList >() << ( QList< QPolygonF >() << points ); } @@ -176,29 +177,6 @@ QList > QgsLegendPatchShape::toQPolygonF( QgsSymbol::SymbolType return QList< QList >(); } -QList > QgsLegendPatchShape::defaultPatch( QgsSymbol::SymbolType type, QSizeF size ) -{ - switch ( type ) - { - case QgsSymbol::Marker: - return QList< QList< QPolygonF > >() << ( QList< QPolygonF >() << ( QPolygonF() << QPointF( static_cast< int >( size.width() ) / 2, - static_cast< int >( size.height() ) / 2 ) ) ); - - case QgsSymbol::Line: - // we're adding 0.5 to get rid of blurred preview: - // drawing antialiased lines of width 1 at (x,0)-(x,100) creates 2px line - return QList< QList >() << ( QList< QPolygonF >() << ( QPolygonF() << QPointF( 0, static_cast< int >( size.height() ) / 2 + 0.5 ) << QPointF( size.width(), static_cast< int >( size.height() ) / 2 + 0.5 ) ) ); - - case QgsSymbol::Fill: - return QList< QList >() << ( QList< QPolygonF> () << ( QRectF( QPointF( 0, 0 ), QPointF( static_cast< int >( size.width() ), static_cast< int >( size.height() ) ) ) ) ); - - case QgsSymbol::Hybrid: - return QList< QList >(); - } - - return QList< QList >(); -} - void QgsLegendPatchShape::readXml( const QDomElement &element, const QgsReadWriteContext & ) { mGeometry = QgsGeometry::fromWkt( element.attribute( QStringLiteral( "wkt" ) ) ); diff --git a/src/core/layertree/qgslegendpatchshape.h b/src/core/layertree/qgslegendpatchshape.h index 2ca21e533b5..7122045056a 100644 --- a/src/core/layertree/qgslegendpatchshape.h +++ b/src/core/layertree/qgslegendpatchshape.h @@ -126,11 +126,6 @@ class CORE_EXPORT QgsLegendPatchShape */ QList< QList< QPolygonF > > toQPolygonF( QgsSymbol::SymbolType type, QSizeF size ) const; - /** - * Returns the default patch geometry for the given symbol \a type and \a size as a set of QPolygonF objects (parts and rings). - */ - static QList< QList< QPolygonF > > defaultPatch( QgsSymbol::SymbolType type, QSizeF size ); - /** * Read settings from a DOM \a element. * \see writeXml() diff --git a/src/core/symbology/qgsstyle.cpp b/src/core/symbology/qgsstyle.cpp index 2b3389c4d0e..c12997f8284 100644 --- a/src/core/symbology/qgsstyle.cpp +++ b/src/core/symbology/qgsstyle.cpp @@ -22,6 +22,9 @@ #include "qgslogger.h" #include "qgsreadwritecontext.h" #include "qgssettings.h" +#include "qgslegendpatchshape.h" +#include "qgslinestring.h" +#include "qgspolygon.h" #include #include @@ -921,6 +924,61 @@ bool QgsStyle::renameLabelSettings( const QString &oldName, const QString &newNa return result; } +QgsLegendPatchShape QgsStyle::defaultPatch( QgsSymbol::SymbolType type, QSizeF size ) const +{ + if ( type == QgsSymbol::Hybrid ) + return QgsLegendPatchShape(); + + if ( mDefaultPatchCache[ type ].contains( size ) ) + return mDefaultPatchCache[ type ].value( size ); + + QgsGeometry geom; + switch ( type ) + { + case QgsSymbol::Marker: + geom = QgsGeometry( qgis::make_unique< QgsPoint >( static_cast< int >( size.width() ) / 2, static_cast< int >( size.height() ) / 2 ) ); + break; + + case QgsSymbol::Line: + { + // we're adding 0.5 to get rid of blurred preview: + // drawing antialiased lines of width 1 at (x,0)-(x,100) creates 2px line + double y = static_cast< int >( size.height() ) / 2 + 0.5; + geom = QgsGeometry( qgis::make_unique< QgsLineString >( ( QVector< double >() << 0 << size.width() ), + ( QVector< double >() << y << y ) ) ); + break; + } + + case QgsSymbol::Fill: + { + geom = QgsGeometry( qgis::make_unique< QgsPolygon >( + new QgsLineString( QVector< double >() << 0 << static_cast< int >( size.width() ) << static_cast< int >( size.width() ) << 0 << 0, + QVector< double >() << static_cast< int >( size.height() ) << static_cast< int >( size.height() ) << 0 << 0 << static_cast< int >( size.height() ) ) ) ); + break; + } + + case QgsSymbol::Hybrid: + break; + } + + QgsLegendPatchShape res = QgsLegendPatchShape( type, geom, true ); + mDefaultPatchCache[ type ][size ] = res; + return res; +} + +QList > QgsStyle::defaultPatchAsQPolygonF( QgsSymbol::SymbolType type, QSizeF size ) const +{ + if ( type == QgsSymbol::Hybrid ) + return QList >(); + + if ( mDefaultPatchQPolygonFCache[ type ].contains( size ) ) + return mDefaultPatchQPolygonFCache[ type ].value( size ); + + QList > res = defaultPatch( type, size ).toQPolygonF( type, size ); + mDefaultPatchQPolygonFCache[ type ][size ] = res; + return res; +} + QStringList QgsStyle::symbolsOfFavorite( StyleEntity type ) const { if ( !mCurrentDB ) diff --git a/src/core/symbology/qgsstyle.h b/src/core/symbology/qgsstyle.h index 0bbc2fe4535..26cae74e372 100644 --- a/src/core/symbology/qgsstyle.h +++ b/src/core/symbology/qgsstyle.h @@ -28,6 +28,7 @@ #include "qgssymbollayerutils.h" // QgsStringMap #include "qgstextrenderer.h" #include "qgspallabeling.h" +#include "layertree/qgslegendpatchshape.h" class QgsSymbol; class QgsSymbolLayer; @@ -584,6 +585,22 @@ class CORE_EXPORT QgsStyle : public QObject */ bool renameLabelSettings( const QString &oldName, const QString &newName ); + /** + * Returns the default legend patch shape for the given symbol \a type. + * + * \see defaultPatchAsQPolygonF() + * \since QGIS 3.14 + */ + QgsLegendPatchShape defaultPatch( QgsSymbol::SymbolType type, QSizeF size ) const; + + /** + * Returns the default patch geometry for the given symbol \a type and \a size as a set of QPolygonF objects (parts and rings). + * + * \see defaultPatch() + * \since QGIS 3.14 + */ + QList< QList< QPolygonF > > defaultPatchAsQPolygonF( QgsSymbol::SymbolType type, QSizeF size ) const; + /** * Creates an on-disk database * @@ -911,6 +928,9 @@ class CORE_EXPORT QgsStyle : public QObject sqlite3_database_unique_ptr mCurrentDB; + mutable QHash< QgsSymbol::SymbolType, QHash< QSizeF, QgsLegendPatchShape > > mDefaultPatchCache; + mutable QHash< QgsSymbol::SymbolType, QHash< QSizeF, QList< QList< QPolygonF > > > > mDefaultPatchQPolygonFCache; + static QgsStyle *sDefaultStyle; //! Convenience function to open the DB and return a sqlite3 object diff --git a/src/core/symbology/qgssymbol.cpp b/src/core/symbology/qgssymbol.cpp index ffe6ea265cc..81d13baa94d 100644 --- a/src/core/symbology/qgssymbol.cpp +++ b/src/core/symbology/qgssymbol.cpp @@ -549,7 +549,7 @@ void QgsSymbol::drawPreviewIcon( QPainter *painter, QSize size, QgsRenderContext const QSizeF targetSize = QSizeF( size.width() - 1, size.height() - 1 ); const QList< QList< QPolygonF > > polys = patchShape ? patchShape->toQPolygonF( QgsSymbol::Fill, targetSize ) - : QgsLegendPatchShape::defaultPatch( QgsSymbol::Fill, targetSize ); + : QgsStyle::defaultStyle()->defaultPatchAsQPolygonF( QgsSymbol::Fill, targetSize ); lsl->startRender( symbolContext ); QgsPaintEffect *effect = lsl->paintEffect(); diff --git a/src/core/symbology/qgssymbollayer.cpp b/src/core/symbology/qgssymbollayer.cpp index 46f0a891f33..3aafec3e75f 100644 --- a/src/core/symbology/qgssymbollayer.cpp +++ b/src/core/symbology/qgssymbollayer.cpp @@ -29,6 +29,7 @@ #include "qgsapplication.h" #include "qgsmultipoint.h" #include "qgslegendpatchshape.h" +#include "qgsstyle.h" #include #include @@ -453,7 +454,7 @@ void QgsMarkerSymbolLayer::drawPreviewIcon( QgsSymbolRenderContext &context, QSi QgsPaintEffect *effect = paintEffect(); QPolygonF points = context.patchShape() ? context.patchShape()->toQPolygonF( QgsSymbol::Marker, size ).value( 0 ).value( 0 ) - : QgsLegendPatchShape::defaultPatch( QgsSymbol::Marker, size ).value( 0 ).value( 0 ); + : QgsStyle::defaultStyle()->defaultPatchAsQPolygonF( QgsSymbol::Marker, size ).value( 0 ).value( 0 ); std::unique_ptr< QgsEffectPainter > effectPainter; if ( effect && effect->enabled() ) @@ -640,7 +641,7 @@ QgsMapUnitScale QgsLineSymbolLayer::mapUnitScale() const void QgsLineSymbolLayer::drawPreviewIcon( QgsSymbolRenderContext &context, QSize size ) { const QList< QList< QPolygonF > > points = context.patchShape() ? context.patchShape()->toQPolygonF( QgsSymbol::Line, size ) - : QgsLegendPatchShape::defaultPatch( QgsSymbol::Line, size ); + : QgsStyle::defaultStyle()->defaultPatchAsQPolygonF( QgsSymbol::Line, size ); startRender( context ); QgsPaintEffect *effect = paintEffect(); @@ -700,7 +701,7 @@ double QgsLineSymbolLayer::dxfWidth( const QgsDxfExport &e, QgsSymbolRenderConte void QgsFillSymbolLayer::drawPreviewIcon( QgsSymbolRenderContext &context, QSize size ) { const QList< QList< QPolygonF > > polys = context.patchShape() ? context.patchShape()->toQPolygonF( QgsSymbol::Fill, size ) - : QgsLegendPatchShape::defaultPatch( QgsSymbol::Fill, size ); + : QgsStyle::defaultStyle()->defaultPatchAsQPolygonF( QgsSymbol::Fill, size ); startRender( context ); QgsPaintEffect *effect = paintEffect(); diff --git a/tests/src/python/test_qgslegendpatchshape.py b/tests/src/python/test_qgslegendpatchshape.py index bc423fd3797..fe5910667c5 100644 --- a/tests/src/python/test_qgslegendpatchshape.py +++ b/tests/src/python/test_qgslegendpatchshape.py @@ -28,7 +28,8 @@ from qgis.core import (QgsLegendPatchShape, QgsMarkerSymbol, QgsRenderChecker, QgsReadWriteContext, - QgsRenderContext + QgsRenderContext, + QgsStyle ) from qgis.PyQt.QtXml import QDomDocument, QDomElement @@ -83,28 +84,28 @@ class TestQgsLegendPatchShape(unittest.TestCase): self.assertTrue(shape.isNull()) def testDefault(self): - self.assertEqual(QgsLegendPatchShape.defaultPatch(QgsSymbol.Hybrid, QSizeF(1, 1)), []) - self.assertEqual(QgsLegendPatchShape.defaultPatch(QgsSymbol.Hybrid, QSizeF(10, 10)), []) + self.assertEqual(QgsStyle.defaultStyle().defaultPatchAsQPolygonF(QgsSymbol.Hybrid, QSizeF(1, 1)), []) + self.assertEqual(QgsStyle.defaultStyle().defaultPatchAsQPolygonF(QgsSymbol.Hybrid, QSizeF(10, 10)), []) # markers - self.assertEqual(self.polys_to_list(QgsLegendPatchShape.defaultPatch(QgsSymbol.Marker, QSizeF(1, 1))), [[[[0.0, 0.0]]]]) - self.assertEqual(self.polys_to_list(QgsLegendPatchShape.defaultPatch(QgsSymbol.Marker, QSizeF(2, 2))), + self.assertEqual(self.polys_to_list(QgsStyle.defaultStyle().defaultPatchAsQPolygonF(QgsSymbol.Marker, QSizeF(1, 1))), [[[[0.0, 0.0]]]]) + self.assertEqual(self.polys_to_list(QgsStyle.defaultStyle().defaultPatchAsQPolygonF(QgsSymbol.Marker, QSizeF(2, 2))), [[[[1.0, 1.0]]]]) - self.assertEqual(self.polys_to_list(QgsLegendPatchShape.defaultPatch(QgsSymbol.Marker, QSizeF(10, 2))), [[[[5.0, 1.0]]]]) + self.assertEqual(self.polys_to_list(QgsStyle.defaultStyle().defaultPatchAsQPolygonF(QgsSymbol.Marker, QSizeF(10, 2))), [[[[5.0, 1.0]]]]) # lines - self.assertEqual(self.polys_to_list(QgsLegendPatchShape.defaultPatch(QgsSymbol.Line, QSizeF(1, 1))), [[[[0.0, 0.5], [1.0, 0.5]]]]) - self.assertEqual(self.polys_to_list(QgsLegendPatchShape.defaultPatch(QgsSymbol.Line, QSizeF(10, 2))), [[[[0.0, 1.5], [10.0, 1.5]]]]) - self.assertEqual(self.polys_to_list(QgsLegendPatchShape.defaultPatch(QgsSymbol.Line, QSizeF(9, 3))), [[[[0.0, 1.5], [9.0, 1.5]]]]) + self.assertEqual(self.polys_to_list(QgsStyle.defaultStyle().defaultPatchAsQPolygonF(QgsSymbol.Line, QSizeF(1, 1))), [[[[0.0, 0.5], [1.0, 0.5]]]]) + self.assertEqual(self.polys_to_list(QgsStyle.defaultStyle().defaultPatchAsQPolygonF(QgsSymbol.Line, QSizeF(10, 2))), [[[[0.0, 1.5], [10.0, 1.5]]]]) + self.assertEqual(self.polys_to_list(QgsStyle.defaultStyle().defaultPatchAsQPolygonF(QgsSymbol.Line, QSizeF(9, 3))), [[[[0.0, 1.5], [9.0, 1.5]]]]) # fills - self.assertEqual(self.polys_to_list(QgsLegendPatchShape.defaultPatch(QgsSymbol.Fill, QSizeF(1, 1))), [[[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0], [0.0, 0.0]]]]) - self.assertEqual(self.polys_to_list(QgsLegendPatchShape.defaultPatch(QgsSymbol.Fill, QSizeF(10, 2))), [[[[0.0, 0.0], [10.0, 0.0], [10.0, 2.0], [0.0, 2.0], [0.0, 0.0]]]]) + self.assertEqual(self.polys_to_list(QgsStyle.defaultStyle().defaultPatchAsQPolygonF(QgsSymbol.Fill, QSizeF(1, 1))), [[[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0], [0.0, 0.0]]]]) + self.assertEqual(self.polys_to_list(QgsStyle.defaultStyle().defaultPatchAsQPolygonF(QgsSymbol.Fill, QSizeF(10, 2))), [[[[0.0, 0.0], [10.0, 0.0], [10.0, 2.0], [0.0, 2.0], [0.0, 0.0]]]]) def testMarkers(self): # shouldn't matter what a point geometry is, it will always be rendered in center of symbol patch shape = QgsLegendPatchShape(QgsSymbol.Marker, QgsGeometry.fromWkt('Point( 5 5 )'), False) - self.assertEqual(self.polys_to_list(shape.toQPolygonF(QgsSymbol.Marker, QSizeF(1, 1))), [[[[0.5, 0.5]]]]) + self.assertEqual(self.polys_to_list(shape.toQPolygonF(QgsSymbol.Marker, QSizeF(1, 1))), [[[[0, 0]]]]) self.assertEqual(self.polys_to_list(shape.toQPolygonF(QgsSymbol.Marker, QSizeF(10, 2))), [[[[5.0, 1.0]]]]) # requesting different symbol type, should return default