mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-13 00:03:09 -04:00
[FEATURE][labels] New text "background" setting for marker symbol backgrounds
Allows for rendering a marker symbol in the background of labels (complementing the existing shapes/SVG choices). This allows use of QGIS marker symbology as a background symbol behind labels (e.g. for highway shield labels)
This commit is contained in:
parent
0bc48b7b6a
commit
da79b6f0e6
@ -268,7 +268,8 @@ Container for settings relating to a text background object.
|
||||
ShapeSquare,
|
||||
ShapeEllipse,
|
||||
ShapeCircle,
|
||||
ShapeSVG
|
||||
ShapeSVG,
|
||||
ShapeMarkerSymbol,
|
||||
};
|
||||
|
||||
enum SizeType
|
||||
@ -344,6 +345,34 @@ QgsTextBackgroundSettings.ShapeSVG. The path must be absolute.
|
||||
:param file: Absolute SVG file path
|
||||
|
||||
.. seealso:: :py:func:`svgFile`
|
||||
%End
|
||||
|
||||
QgsMarkerSymbol *markerSymbol() const;
|
||||
%Docstring
|
||||
Returns the marker symbol to be rendered in the background. Ownership remains with
|
||||
the background settings.
|
||||
|
||||
.. note::
|
||||
|
||||
This is only used when the type() is QgsTextBackgroundSettings.ShapeMarkerSymbol.
|
||||
|
||||
.. seealso:: :py:func:`setMarkerSymbol`
|
||||
|
||||
.. versionadded:: 3.10
|
||||
%End
|
||||
|
||||
void setMarkerSymbol( QgsMarkerSymbol *symbol /Transfer/ );
|
||||
%Docstring
|
||||
Sets the current marker ``symbol`` for the background shape. Ownership is transferred
|
||||
to the background settings.
|
||||
|
||||
.. note::
|
||||
|
||||
This is only used when the type() is QgsTextBackgroundSettings.ShapeMarkerSymbol.
|
||||
|
||||
.. seealso:: :py:func:`markerSymbol`
|
||||
|
||||
.. versionadded:: 3.10
|
||||
%End
|
||||
|
||||
SizeType sizeType() const;
|
||||
|
@ -12,7 +12,7 @@
|
||||
|
||||
|
||||
|
||||
class QgsTextFormatWidget : QWidget, protected Ui::QgsTextFormatWidgetBase
|
||||
class QgsTextFormatWidget : QWidget, QgsExpressionContextGenerator, protected Ui::QgsTextFormatWidgetBase
|
||||
{
|
||||
%Docstring
|
||||
A widget for customizing text formatting settings.
|
||||
@ -111,6 +111,9 @@ Controls whether data defined alignment buttons are enabled.
|
||||
:param enable: set to ``True`` to enable alignment controls
|
||||
%End
|
||||
|
||||
virtual QgsExpressionContext createExpressionContext() const;
|
||||
|
||||
|
||||
|
||||
protected slots:
|
||||
|
||||
|
@ -2865,6 +2865,10 @@ void QgsPalLayerSettings::parseShapeBackground( QgsRenderContext &context )
|
||||
{
|
||||
shpkind = QgsTextBackgroundSettings::ShapeSVG;
|
||||
}
|
||||
else if ( skind.compare( QLatin1String( "marker" ), Qt::CaseInsensitive ) == 0 )
|
||||
{
|
||||
shpkind = QgsTextBackgroundSettings::ShapeMarkerSymbol;
|
||||
}
|
||||
shapeKind = shpkind;
|
||||
dataDefinedValues.insert( QgsPalLayerSettings::ShapeKind, QVariant( static_cast< int >( shpkind ) ) );
|
||||
}
|
||||
@ -2930,7 +2934,16 @@ void QgsPalLayerSettings::parseShapeBackground( QgsRenderContext &context )
|
||||
{
|
||||
skip = true;
|
||||
}
|
||||
if ( shapeKind == QgsTextBackgroundSettings::ShapeMarkerSymbol
|
||||
&& ( !background.markerSymbol()
|
||||
|| ( background.markerSymbol()
|
||||
&& shpSizeType == QgsTextBackgroundSettings::SizeFixed
|
||||
&& ddShpSizeX == 0.0 ) ) )
|
||||
{
|
||||
skip = true;
|
||||
}
|
||||
if ( shapeKind != QgsTextBackgroundSettings::ShapeSVG
|
||||
&& shapeKind != QgsTextBackgroundSettings::ShapeMarkerSymbol
|
||||
&& shpSizeType == QgsTextBackgroundSettings::SizeFixed
|
||||
&& ( ddShpSizeX == 0.0 || ddShpSizeY == 0.0 ) )
|
||||
{
|
||||
|
@ -177,13 +177,12 @@ void QgsTextBufferSettings::setBlendMode( QPainter::CompositionMode mode )
|
||||
|
||||
QgsPaintEffect *QgsTextBufferSettings::paintEffect() const
|
||||
{
|
||||
return d->paintEffect;
|
||||
return d->paintEffect.get();
|
||||
}
|
||||
|
||||
void QgsTextBufferSettings::setPaintEffect( QgsPaintEffect *effect )
|
||||
{
|
||||
delete d->paintEffect;
|
||||
d->paintEffect = effect;
|
||||
d->paintEffect.reset( effect );
|
||||
}
|
||||
|
||||
void QgsTextBufferSettings::readFromLayer( QgsVectorLayer *layer )
|
||||
@ -337,7 +336,7 @@ QDomElement QgsTextBufferSettings::writeXml( QDomDocument &doc ) const
|
||||
textBufferElem.setAttribute( QStringLiteral( "bufferOpacity" ), d->opacity );
|
||||
textBufferElem.setAttribute( QStringLiteral( "bufferJoinStyle" ), static_cast< unsigned int >( d->joinStyle ) );
|
||||
textBufferElem.setAttribute( QStringLiteral( "bufferBlendMode" ), QgsPainting::getBlendModeEnum( d->blendMode ) );
|
||||
if ( d->paintEffect && !QgsPaintEffectRegistry::isDefaultStack( d->paintEffect ) )
|
||||
if ( d->paintEffect && !QgsPaintEffectRegistry::isDefaultStack( d->paintEffect.get() ) )
|
||||
d->paintEffect->saveProperties( doc, textBufferElem );
|
||||
return textBufferElem;
|
||||
}
|
||||
@ -399,6 +398,16 @@ void QgsTextBackgroundSettings::setSvgFile( const QString &file )
|
||||
d->svgFile = file;
|
||||
}
|
||||
|
||||
QgsMarkerSymbol *QgsTextBackgroundSettings::markerSymbol() const
|
||||
{
|
||||
return d->markerSymbol.get();
|
||||
}
|
||||
|
||||
void QgsTextBackgroundSettings::setMarkerSymbol( QgsMarkerSymbol *symbol )
|
||||
{
|
||||
d->markerSymbol.reset( symbol );
|
||||
}
|
||||
|
||||
QgsTextBackgroundSettings::SizeType QgsTextBackgroundSettings::sizeType() const
|
||||
{
|
||||
return d->sizeType;
|
||||
@ -601,13 +610,12 @@ void QgsTextBackgroundSettings::setJoinStyle( Qt::PenJoinStyle style )
|
||||
|
||||
QgsPaintEffect *QgsTextBackgroundSettings::paintEffect() const
|
||||
{
|
||||
return d->paintEffect;
|
||||
return d->paintEffect.get();
|
||||
}
|
||||
|
||||
void QgsTextBackgroundSettings::setPaintEffect( QgsPaintEffect *effect )
|
||||
{
|
||||
delete d->paintEffect;
|
||||
d->paintEffect = effect;
|
||||
d->paintEffect.reset( effect );
|
||||
}
|
||||
|
||||
void QgsTextBackgroundSettings::readFromLayer( QgsVectorLayer *layer )
|
||||
@ -861,6 +869,12 @@ void QgsTextBackgroundSettings::readXml( const QDomElement &elem, const QgsReadW
|
||||
setPaintEffect( QgsApplication::paintEffectRegistry()->createEffect( effectElem ) );
|
||||
else
|
||||
setPaintEffect( nullptr );
|
||||
|
||||
const QDomElement symbolElem = backgroundElem.firstChildElement( QStringLiteral( "symbol" ) );
|
||||
if ( !symbolElem.isNull() )
|
||||
setMarkerSymbol( QgsSymbolLayerUtils::loadSymbol< QgsMarkerSymbol >( symbolElem, context ) );
|
||||
else
|
||||
setMarkerSymbol( nullptr );
|
||||
}
|
||||
|
||||
QDomElement QgsTextBackgroundSettings::writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const
|
||||
@ -892,8 +906,12 @@ QDomElement QgsTextBackgroundSettings::writeXml( QDomDocument &doc, const QgsRea
|
||||
backgroundElem.setAttribute( QStringLiteral( "shapeJoinStyle" ), static_cast< unsigned int >( d->joinStyle ) );
|
||||
backgroundElem.setAttribute( QStringLiteral( "shapeOpacity" ), d->opacity );
|
||||
backgroundElem.setAttribute( QStringLiteral( "shapeBlendMode" ), QgsPainting::getBlendModeEnum( d->blendMode ) );
|
||||
if ( d->paintEffect && !QgsPaintEffectRegistry::isDefaultStack( d->paintEffect ) )
|
||||
if ( d->paintEffect && !QgsPaintEffectRegistry::isDefaultStack( d->paintEffect.get() ) )
|
||||
d->paintEffect->saveProperties( doc, backgroundElem );
|
||||
|
||||
if ( d->markerSymbol )
|
||||
backgroundElem.appendChild( QgsSymbolLayerUtils::saveSymbol( QStringLiteral( "marker" ), d->markerSymbol.get(), doc, context ) );
|
||||
|
||||
return backgroundElem;
|
||||
}
|
||||
|
||||
@ -1857,7 +1875,7 @@ QgsTextFormat QgsTextRenderer::updateShadowPosition( const QgsTextFormat &format
|
||||
return format;
|
||||
|
||||
QgsTextFormat tmpFormat = format;
|
||||
if ( tmpFormat.background().enabled() )
|
||||
if ( tmpFormat.background().enabled() && tmpFormat.background().type() != QgsTextBackgroundSettings::ShapeMarkerSymbol ) // background shadow not compatible with marker symbol backgrounds
|
||||
{
|
||||
tmpFormat.shadow().setShadowPlacement( QgsTextShadowSettings::ShadowShape );
|
||||
}
|
||||
@ -2198,264 +2216,291 @@ void QgsTextRenderer::drawBackground( QgsRenderContext &context, QgsTextRenderer
|
||||
|
||||
// TODO: the following label-buffered generated shapes and SVG symbols should be moved into marker symbology classes
|
||||
|
||||
if ( background.type() == QgsTextBackgroundSettings::ShapeSVG )
|
||||
switch ( background.type() )
|
||||
{
|
||||
// all calculations done in shapeSizeUnits, which are then passed to symbology class for painting
|
||||
|
||||
if ( background.svgFile().isEmpty() )
|
||||
return;
|
||||
|
||||
double sizeOut = 0.0;
|
||||
// only one size used for SVG sizing/scaling (no use of shapeSize.y() or Y field in gui)
|
||||
if ( background.sizeType() == QgsTextBackgroundSettings::SizeFixed )
|
||||
case QgsTextBackgroundSettings::ShapeSVG:
|
||||
case QgsTextBackgroundSettings::ShapeMarkerSymbol:
|
||||
{
|
||||
sizeOut = context.convertToPainterUnits( background.size().width(), background.sizeUnit(), background.sizeMapUnitScale() );
|
||||
}
|
||||
else if ( background.sizeType() == QgsTextBackgroundSettings::SizeBuffer )
|
||||
{
|
||||
sizeOut = std::max( component.size.width(), component.size.height() );
|
||||
double bufferSize = context.convertToPainterUnits( background.size().width(), background.sizeUnit(), background.sizeMapUnitScale() );
|
||||
// all calculations done in shapeSizeUnits, which are then passed to symbology class for painting
|
||||
|
||||
// add buffer
|
||||
sizeOut += bufferSize * 2;
|
||||
}
|
||||
if ( background.type() == QgsTextBackgroundSettings::ShapeSVG && background.svgFile().isEmpty() )
|
||||
return;
|
||||
|
||||
// don't bother rendering symbols smaller than 1x1 pixels in size
|
||||
// TODO: add option to not show any svgs under/over a certain size
|
||||
if ( sizeOut < 1.0 )
|
||||
return;
|
||||
if ( background.type() == QgsTextBackgroundSettings::ShapeMarkerSymbol && !background.markerSymbol() )
|
||||
return;
|
||||
|
||||
QgsStringMap map; // for SVG symbology marker
|
||||
map[QStringLiteral( "name" )] = background.svgFile().trimmed();
|
||||
map[QStringLiteral( "size" )] = QString::number( sizeOut );
|
||||
map[QStringLiteral( "size_unit" )] = QgsUnitTypes::encodeUnit( QgsUnitTypes::RenderPixels );
|
||||
map[QStringLiteral( "angle" )] = QString::number( 0.0 ); // angle is handled by this local painter
|
||||
double sizeOut = 0.0;
|
||||
// only one size used for SVG sizing/scaling (no use of shapeSize.y() or Y field in gui)
|
||||
if ( background.sizeType() == QgsTextBackgroundSettings::SizeFixed )
|
||||
{
|
||||
sizeOut = context.convertToPainterUnits( background.size().width(), background.sizeUnit(), background.sizeMapUnitScale() );
|
||||
}
|
||||
else if ( background.sizeType() == QgsTextBackgroundSettings::SizeBuffer )
|
||||
{
|
||||
sizeOut = std::max( component.size.width(), component.size.height() );
|
||||
double bufferSize = context.convertToPainterUnits( background.size().width(), background.sizeUnit(), background.sizeMapUnitScale() );
|
||||
|
||||
// offset is handled by this local painter
|
||||
// TODO: see why the marker renderer doesn't seem to translate offset *after* applying rotation
|
||||
//map["offset"] = QgsSymbolLayerUtils::encodePoint( tmpLyr.shapeOffset );
|
||||
//map["offset_unit"] = QgsUnitTypes::encodeUnit(
|
||||
// tmpLyr.shapeOffsetUnits == QgsPalLayerSettings::MapUnits ? QgsUnitTypes::MapUnit : QgsUnitTypes::MM );
|
||||
// add buffer
|
||||
sizeOut += bufferSize * 2;
|
||||
}
|
||||
|
||||
map[QStringLiteral( "fill" )] = background.fillColor().name();
|
||||
map[QStringLiteral( "outline" )] = background.strokeColor().name();
|
||||
map[QStringLiteral( "outline-width" )] = QString::number( background.strokeWidth() );
|
||||
map[QStringLiteral( "outline_width_unit" )] = QgsUnitTypes::encodeUnit( background.strokeWidthUnit() );
|
||||
// don't bother rendering symbols smaller than 1x1 pixels in size
|
||||
// TODO: add option to not show any svgs under/over a certain size
|
||||
if ( sizeOut < 1.0 )
|
||||
return;
|
||||
|
||||
if ( format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowShape )
|
||||
{
|
||||
QgsTextShadowSettings shadow = format.shadow();
|
||||
// configure SVG shadow specs
|
||||
QgsStringMap shdwmap( map );
|
||||
shdwmap[QStringLiteral( "fill" )] = shadow.color().name();
|
||||
shdwmap[QStringLiteral( "outline" )] = shadow.color().name();
|
||||
shdwmap[QStringLiteral( "size" )] = QString::number( sizeOut );
|
||||
std::unique_ptr< QgsMarkerSymbol > renderedSymbol;
|
||||
if ( background.type() == QgsTextBackgroundSettings::ShapeSVG )
|
||||
{
|
||||
QgsStringMap map; // for SVG symbology marker
|
||||
map[QStringLiteral( "name" )] = background.svgFile().trimmed();
|
||||
map[QStringLiteral( "size" )] = QString::number( sizeOut );
|
||||
map[QStringLiteral( "size_unit" )] = QgsUnitTypes::encodeUnit( QgsUnitTypes::RenderPixels );
|
||||
map[QStringLiteral( "angle" )] = QString::number( 0.0 ); // angle is handled by this local painter
|
||||
|
||||
// store SVG's drawing in QPicture for drop shadow call
|
||||
QPicture svgPict;
|
||||
QPainter svgp;
|
||||
svgp.begin( &svgPict );
|
||||
// offset is handled by this local painter
|
||||
// TODO: see why the marker renderer doesn't seem to translate offset *after* applying rotation
|
||||
//map["offset"] = QgsSymbolLayerUtils::encodePoint( tmpLyr.shapeOffset );
|
||||
//map["offset_unit"] = QgsUnitTypes::encodeUnit(
|
||||
// tmpLyr.shapeOffsetUnits == QgsPalLayerSettings::MapUnits ? QgsUnitTypes::MapUnit : QgsUnitTypes::MM );
|
||||
|
||||
// draw shadow symbol
|
||||
map[QStringLiteral( "fill" )] = background.fillColor().name();
|
||||
map[QStringLiteral( "outline" )] = background.strokeColor().name();
|
||||
map[QStringLiteral( "outline-width" )] = QString::number( background.strokeWidth() );
|
||||
map[QStringLiteral( "outline_width_unit" )] = QgsUnitTypes::encodeUnit( background.strokeWidthUnit() );
|
||||
|
||||
// clone current render context map unit/mm conversion factors, but not
|
||||
// other map canvas parameters, then substitute this painter for use in symbology painting
|
||||
// NOTE: this is because the shadow needs to be scaled correctly for output to map canvas,
|
||||
// but will be created relative to the SVG's computed size, not the current map canvas
|
||||
QgsRenderContext shdwContext;
|
||||
shdwContext.setMapToPixel( context.mapToPixel() );
|
||||
shdwContext.setScaleFactor( context.scaleFactor() );
|
||||
shdwContext.setPainter( &svgp );
|
||||
if ( format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowShape )
|
||||
{
|
||||
QgsTextShadowSettings shadow = format.shadow();
|
||||
// configure SVG shadow specs
|
||||
QgsStringMap shdwmap( map );
|
||||
shdwmap[QStringLiteral( "fill" )] = shadow.color().name();
|
||||
shdwmap[QStringLiteral( "outline" )] = shadow.color().name();
|
||||
shdwmap[QStringLiteral( "size" )] = QString::number( sizeOut );
|
||||
|
||||
QgsSymbolLayer *symShdwL = QgsSvgMarkerSymbolLayer::create( shdwmap );
|
||||
QgsSvgMarkerSymbolLayer *svgShdwM = static_cast<QgsSvgMarkerSymbolLayer *>( symShdwL );
|
||||
QgsSymbolRenderContext svgShdwContext( shdwContext, QgsUnitTypes::RenderUnknownUnit, background.opacity() );
|
||||
// store SVG's drawing in QPicture for drop shadow call
|
||||
QPicture svgPict;
|
||||
QPainter svgp;
|
||||
svgp.begin( &svgPict );
|
||||
|
||||
svgShdwM->renderPoint( QPointF( sizeOut / 2, -sizeOut / 2 ), svgShdwContext );
|
||||
svgp.end();
|
||||
// draw shadow symbol
|
||||
|
||||
component.picture = svgPict;
|
||||
// TODO: when SVG symbol's stroke width/units is fixed in QgsSvgCache, adjust for it here
|
||||
component.pictureBuffer = 0.0;
|
||||
// clone current render context map unit/mm conversion factors, but not
|
||||
// other map canvas parameters, then substitute this painter for use in symbology painting
|
||||
// NOTE: this is because the shadow needs to be scaled correctly for output to map canvas,
|
||||
// but will be created relative to the SVG's computed size, not the current map canvas
|
||||
QgsRenderContext shdwContext;
|
||||
shdwContext.setMapToPixel( context.mapToPixel() );
|
||||
shdwContext.setScaleFactor( context.scaleFactor() );
|
||||
shdwContext.setPainter( &svgp );
|
||||
|
||||
component.size = QSizeF( sizeOut, sizeOut );
|
||||
component.offset = QPointF( 0.0, 0.0 );
|
||||
QgsSymbolLayer *symShdwL = QgsSvgMarkerSymbolLayer::create( shdwmap );
|
||||
QgsSvgMarkerSymbolLayer *svgShdwM = static_cast<QgsSvgMarkerSymbolLayer *>( symShdwL );
|
||||
QgsSymbolRenderContext svgShdwContext( shdwContext, QgsUnitTypes::RenderUnknownUnit, background.opacity() );
|
||||
|
||||
// rotate about origin center of SVG
|
||||
svgShdwM->renderPoint( QPointF( sizeOut / 2, -sizeOut / 2 ), svgShdwContext );
|
||||
svgp.end();
|
||||
|
||||
component.picture = svgPict;
|
||||
// TODO: when SVG symbol's stroke width/units is fixed in QgsSvgCache, adjust for it here
|
||||
component.pictureBuffer = 0.0;
|
||||
|
||||
component.size = QSizeF( sizeOut, sizeOut );
|
||||
component.offset = QPointF( 0.0, 0.0 );
|
||||
|
||||
// rotate about origin center of SVG
|
||||
p->save();
|
||||
p->translate( component.center.x(), component.center.y() );
|
||||
p->rotate( component.rotation );
|
||||
double xoff = context.convertToPainterUnits( background.offset().x(), background.offsetUnit(), background.offsetMapUnitScale() );
|
||||
double yoff = context.convertToPainterUnits( background.offset().y(), background.offsetUnit(), background.offsetMapUnitScale() );
|
||||
p->translate( QPointF( xoff, yoff ) );
|
||||
p->rotate( component.rotationOffset );
|
||||
p->translate( -sizeOut / 2, sizeOut / 2 );
|
||||
if ( context.flags() & QgsRenderContext::Antialiasing )
|
||||
{
|
||||
p->setRenderHint( QPainter::Antialiasing );
|
||||
}
|
||||
|
||||
drawShadow( context, component, format );
|
||||
p->restore();
|
||||
|
||||
delete svgShdwM;
|
||||
svgShdwM = nullptr;
|
||||
}
|
||||
renderedSymbol.reset( );
|
||||
|
||||
QgsSymbolLayer *symL = QgsSvgMarkerSymbolLayer::create( map );
|
||||
renderedSymbol.reset( new QgsMarkerSymbol( QgsSymbolLayerList() << symL ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
renderedSymbol.reset( background.markerSymbol()->clone() );
|
||||
renderedSymbol->setSize( sizeOut );
|
||||
renderedSymbol->setSizeUnit( QgsUnitTypes::RenderPixels );
|
||||
}
|
||||
|
||||
renderedSymbol->setOpacity( background.opacity() );
|
||||
|
||||
// draw the actual symbol
|
||||
p->save();
|
||||
if ( context.useAdvancedEffects() )
|
||||
{
|
||||
p->setCompositionMode( background.blendMode() );
|
||||
}
|
||||
if ( context.flags() & QgsRenderContext::Antialiasing )
|
||||
{
|
||||
p->setRenderHint( QPainter::Antialiasing );
|
||||
}
|
||||
p->translate( component.center.x(), component.center.y() );
|
||||
p->rotate( component.rotation );
|
||||
double xoff = context.convertToPainterUnits( background.offset().x(), background.offsetUnit(), background.offsetMapUnitScale() );
|
||||
double yoff = context.convertToPainterUnits( background.offset().y(), background.offsetUnit(), background.offsetMapUnitScale() );
|
||||
p->translate( QPointF( xoff, yoff ) );
|
||||
p->rotate( component.rotationOffset );
|
||||
p->translate( -sizeOut / 2, sizeOut / 2 );
|
||||
|
||||
const QgsFeature f = context.expressionContext().feature();
|
||||
renderedSymbol->startRender( context, context.expressionContext().fields() );
|
||||
renderedSymbol->renderPoint( QPointF( 0, 0 ), &f, context );
|
||||
renderedSymbol->stopRender( context );
|
||||
p->setCompositionMode( QPainter::CompositionMode_SourceOver ); // just to be sure
|
||||
p->restore();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case QgsTextBackgroundSettings::ShapeRectangle:
|
||||
case QgsTextBackgroundSettings::ShapeCircle:
|
||||
case QgsTextBackgroundSettings::ShapeSquare:
|
||||
case QgsTextBackgroundSettings::ShapeEllipse:
|
||||
{
|
||||
double w = component.size.width();
|
||||
double h = component.size.height();
|
||||
|
||||
if ( background.sizeType() == QgsTextBackgroundSettings::SizeFixed )
|
||||
{
|
||||
w = context.convertToPainterUnits( background.size().width(), background.sizeUnit(),
|
||||
background.sizeMapUnitScale() );
|
||||
h = context.convertToPainterUnits( background.size().height(), background.sizeUnit(),
|
||||
background.sizeMapUnitScale() );
|
||||
}
|
||||
else if ( background.sizeType() == QgsTextBackgroundSettings::SizeBuffer )
|
||||
{
|
||||
if ( background.type() == QgsTextBackgroundSettings::ShapeSquare )
|
||||
{
|
||||
if ( w > h )
|
||||
h = w;
|
||||
else if ( h > w )
|
||||
w = h;
|
||||
}
|
||||
else if ( background.type() == QgsTextBackgroundSettings::ShapeCircle )
|
||||
{
|
||||
// start with label bound by circle
|
||||
h = std::sqrt( std::pow( w, 2 ) + std::pow( h, 2 ) );
|
||||
w = h;
|
||||
}
|
||||
else if ( background.type() == QgsTextBackgroundSettings::ShapeEllipse )
|
||||
{
|
||||
// start with label bound by ellipse
|
||||
h = h * M_SQRT1_2 * 2;
|
||||
w = w * M_SQRT1_2 * 2;
|
||||
}
|
||||
|
||||
double bufferWidth = context.convertToPainterUnits( background.size().width(), background.sizeUnit(),
|
||||
background.sizeMapUnitScale() );
|
||||
double bufferHeight = context.convertToPainterUnits( background.size().height(), background.sizeUnit(),
|
||||
background.sizeMapUnitScale() );
|
||||
|
||||
w += bufferWidth * 2;
|
||||
h += bufferHeight * 2;
|
||||
}
|
||||
|
||||
// offsets match those of symbology: -x = left, -y = up
|
||||
QRectF rect( -w / 2.0, - h / 2.0, w, h );
|
||||
|
||||
if ( rect.isNull() )
|
||||
return;
|
||||
|
||||
p->save();
|
||||
if ( context.flags() & QgsRenderContext::Antialiasing )
|
||||
{
|
||||
p->setRenderHint( QPainter::Antialiasing );
|
||||
}
|
||||
p->translate( QPointF( component.center.x(), component.center.y() ) );
|
||||
p->rotate( component.rotation );
|
||||
double xoff = context.convertToPainterUnits( background.offset().x(), background.offsetUnit(), background.offsetMapUnitScale() );
|
||||
double yoff = context.convertToPainterUnits( background.offset().y(), background.offsetUnit(), background.offsetMapUnitScale() );
|
||||
p->translate( QPointF( xoff, yoff ) );
|
||||
p->rotate( component.rotationOffset );
|
||||
|
||||
drawShadow( context, component, format );
|
||||
p->restore();
|
||||
double penSize = context.convertToPainterUnits( background.strokeWidth(), background.strokeWidthUnit(), background.strokeWidthMapUnitScale() );
|
||||
|
||||
delete svgShdwM;
|
||||
svgShdwM = nullptr;
|
||||
}
|
||||
|
||||
// draw the actual symbol
|
||||
QgsSymbolLayer *symL = QgsSvgMarkerSymbolLayer::create( map );
|
||||
QgsSvgMarkerSymbolLayer *svgM = static_cast<QgsSvgMarkerSymbolLayer *>( symL );
|
||||
QgsSymbolRenderContext svgContext( context, QgsUnitTypes::RenderUnknownUnit, background.opacity() );
|
||||
|
||||
p->save();
|
||||
if ( context.useAdvancedEffects() )
|
||||
{
|
||||
p->setCompositionMode( background.blendMode() );
|
||||
}
|
||||
if ( context.flags() & QgsRenderContext::Antialiasing )
|
||||
{
|
||||
p->setRenderHint( QPainter::Antialiasing );
|
||||
}
|
||||
p->translate( component.center.x(), component.center.y() );
|
||||
p->rotate( component.rotation );
|
||||
double xoff = context.convertToPainterUnits( background.offset().x(), background.offsetUnit(), background.offsetMapUnitScale() );
|
||||
double yoff = context.convertToPainterUnits( background.offset().y(), background.offsetUnit(), background.offsetMapUnitScale() );
|
||||
p->translate( QPointF( xoff, yoff ) );
|
||||
p->rotate( component.rotationOffset );
|
||||
svgM->renderPoint( QPointF( 0, 0 ), svgContext );
|
||||
p->setCompositionMode( QPainter::CompositionMode_SourceOver ); // just to be sure
|
||||
p->restore();
|
||||
|
||||
delete svgM;
|
||||
svgM = nullptr;
|
||||
|
||||
}
|
||||
else // Generated Shapes
|
||||
{
|
||||
double w = component.size.width();
|
||||
double h = component.size.height();
|
||||
|
||||
if ( background.sizeType() == QgsTextBackgroundSettings::SizeFixed )
|
||||
{
|
||||
w = context.convertToPainterUnits( background.size().width(), background.sizeUnit(),
|
||||
background.sizeMapUnitScale() );
|
||||
h = context.convertToPainterUnits( background.size().height(), background.sizeUnit(),
|
||||
background.sizeMapUnitScale() );
|
||||
}
|
||||
else if ( background.sizeType() == QgsTextBackgroundSettings::SizeBuffer )
|
||||
{
|
||||
if ( background.type() == QgsTextBackgroundSettings::ShapeSquare )
|
||||
QPen pen;
|
||||
if ( background.strokeWidth() > 0 )
|
||||
{
|
||||
if ( w > h )
|
||||
h = w;
|
||||
else if ( h > w )
|
||||
w = h;
|
||||
}
|
||||
else if ( background.type() == QgsTextBackgroundSettings::ShapeCircle )
|
||||
{
|
||||
// start with label bound by circle
|
||||
h = std::sqrt( std::pow( w, 2 ) + std::pow( h, 2 ) );
|
||||
w = h;
|
||||
}
|
||||
else if ( background.type() == QgsTextBackgroundSettings::ShapeEllipse )
|
||||
{
|
||||
// start with label bound by ellipse
|
||||
h = h * M_SQRT1_2 * 2;
|
||||
w = w * M_SQRT1_2 * 2;
|
||||
}
|
||||
|
||||
double bufferWidth = context.convertToPainterUnits( background.size().width(), background.sizeUnit(),
|
||||
background.sizeMapUnitScale() );
|
||||
double bufferHeight = context.convertToPainterUnits( background.size().height(), background.sizeUnit(),
|
||||
background.sizeMapUnitScale() );
|
||||
|
||||
w += bufferWidth * 2;
|
||||
h += bufferHeight * 2;
|
||||
}
|
||||
|
||||
// offsets match those of symbology: -x = left, -y = up
|
||||
QRectF rect( -w / 2.0, - h / 2.0, w, h );
|
||||
|
||||
if ( rect.isNull() )
|
||||
return;
|
||||
|
||||
p->save();
|
||||
if ( context.flags() & QgsRenderContext::Antialiasing )
|
||||
{
|
||||
p->setRenderHint( QPainter::Antialiasing );
|
||||
}
|
||||
p->translate( QPointF( component.center.x(), component.center.y() ) );
|
||||
p->rotate( component.rotation );
|
||||
double xoff = context.convertToPainterUnits( background.offset().x(), background.offsetUnit(), background.offsetMapUnitScale() );
|
||||
double yoff = context.convertToPainterUnits( background.offset().y(), background.offsetUnit(), background.offsetMapUnitScale() );
|
||||
p->translate( QPointF( xoff, yoff ) );
|
||||
p->rotate( component.rotationOffset );
|
||||
|
||||
double penSize = context.convertToPainterUnits( background.strokeWidth(), background.strokeWidthUnit(), background.strokeWidthMapUnitScale() );
|
||||
|
||||
QPen pen;
|
||||
if ( background.strokeWidth() > 0 )
|
||||
{
|
||||
pen.setColor( background.strokeColor() );
|
||||
pen.setWidthF( penSize );
|
||||
if ( background.type() == QgsTextBackgroundSettings::ShapeRectangle )
|
||||
pen.setJoinStyle( background.joinStyle() );
|
||||
}
|
||||
else
|
||||
{
|
||||
pen = Qt::NoPen;
|
||||
}
|
||||
|
||||
// store painting in QPicture for shadow drawing
|
||||
QPicture shapePict;
|
||||
QPainter shapep;
|
||||
shapep.begin( &shapePict );
|
||||
shapep.setPen( pen );
|
||||
shapep.setBrush( background.fillColor() );
|
||||
|
||||
if ( background.type() == QgsTextBackgroundSettings::ShapeRectangle
|
||||
|| background.type() == QgsTextBackgroundSettings::ShapeSquare )
|
||||
{
|
||||
if ( background.radiiUnit() == QgsUnitTypes::RenderPercentage )
|
||||
{
|
||||
shapep.drawRoundedRect( rect, background.radii().width(), background.radii().height(), Qt::RelativeSize );
|
||||
pen.setColor( background.strokeColor() );
|
||||
pen.setWidthF( penSize );
|
||||
if ( background.type() == QgsTextBackgroundSettings::ShapeRectangle )
|
||||
pen.setJoinStyle( background.joinStyle() );
|
||||
}
|
||||
else
|
||||
{
|
||||
double xRadius = context.convertToPainterUnits( background.radii().width(), background.radiiUnit(), background.radiiMapUnitScale() );
|
||||
double yRadius = context.convertToPainterUnits( background.radii().height(), background.radiiUnit(), background.radiiMapUnitScale() );
|
||||
shapep.drawRoundedRect( rect, xRadius, yRadius );
|
||||
pen = Qt::NoPen;
|
||||
}
|
||||
}
|
||||
else if ( background.type() == QgsTextBackgroundSettings::ShapeEllipse
|
||||
|| background.type() == QgsTextBackgroundSettings::ShapeCircle )
|
||||
{
|
||||
shapep.drawEllipse( rect );
|
||||
}
|
||||
shapep.end();
|
||||
|
||||
if ( format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowShape )
|
||||
{
|
||||
component.picture = shapePict;
|
||||
component.pictureBuffer = penSize / 2.0;
|
||||
// store painting in QPicture for shadow drawing
|
||||
QPicture shapePict;
|
||||
QPainter shapep;
|
||||
shapep.begin( &shapePict );
|
||||
shapep.setPen( pen );
|
||||
shapep.setBrush( background.fillColor() );
|
||||
|
||||
component.size = rect.size();
|
||||
component.offset = QPointF( rect.width() / 2, -rect.height() / 2 );
|
||||
drawShadow( context, component, format );
|
||||
if ( background.type() == QgsTextBackgroundSettings::ShapeRectangle
|
||||
|| background.type() == QgsTextBackgroundSettings::ShapeSquare )
|
||||
{
|
||||
if ( background.radiiUnit() == QgsUnitTypes::RenderPercentage )
|
||||
{
|
||||
shapep.drawRoundedRect( rect, background.radii().width(), background.radii().height(), Qt::RelativeSize );
|
||||
}
|
||||
else
|
||||
{
|
||||
double xRadius = context.convertToPainterUnits( background.radii().width(), background.radiiUnit(), background.radiiMapUnitScale() );
|
||||
double yRadius = context.convertToPainterUnits( background.radii().height(), background.radiiUnit(), background.radiiMapUnitScale() );
|
||||
shapep.drawRoundedRect( rect, xRadius, yRadius );
|
||||
}
|
||||
}
|
||||
else if ( background.type() == QgsTextBackgroundSettings::ShapeEllipse
|
||||
|| background.type() == QgsTextBackgroundSettings::ShapeCircle )
|
||||
{
|
||||
shapep.drawEllipse( rect );
|
||||
}
|
||||
shapep.end();
|
||||
|
||||
if ( format.shadow().enabled() && format.shadow().shadowPlacement() == QgsTextShadowSettings::ShadowShape )
|
||||
{
|
||||
component.picture = shapePict;
|
||||
component.pictureBuffer = penSize / 2.0;
|
||||
|
||||
component.size = rect.size();
|
||||
component.offset = QPointF( rect.width() / 2, -rect.height() / 2 );
|
||||
drawShadow( context, component, format );
|
||||
}
|
||||
|
||||
p->setOpacity( background.opacity() );
|
||||
if ( context.useAdvancedEffects() )
|
||||
{
|
||||
p->setCompositionMode( background.blendMode() );
|
||||
}
|
||||
|
||||
// scale for any print output or image saving @ specific dpi
|
||||
p->scale( component.dpiRatio, component.dpiRatio );
|
||||
_fixQPictureDPI( p );
|
||||
p->drawPicture( 0, 0, shapePict );
|
||||
p->restore();
|
||||
break;
|
||||
}
|
||||
|
||||
p->setOpacity( background.opacity() );
|
||||
if ( context.useAdvancedEffects() )
|
||||
{
|
||||
p->setCompositionMode( background.blendMode() );
|
||||
}
|
||||
|
||||
// scale for any print output or image saving @ specific dpi
|
||||
p->scale( component.dpiRatio, component.dpiRatio );
|
||||
_fixQPictureDPI( p );
|
||||
p->drawPicture( 0, 0, shapePict );
|
||||
p->restore();
|
||||
}
|
||||
|
||||
if ( background.paintEffect() && background.paintEffect()->enabled() )
|
||||
{
|
||||
background.paintEffect()->end( context );
|
||||
|
@ -34,6 +34,7 @@ class QgsTextShadowSettingsPrivate;
|
||||
class QgsTextSettingsPrivate;
|
||||
class QgsVectorLayer;
|
||||
class QgsPaintEffect;
|
||||
class QgsMarkerSymbol;
|
||||
|
||||
/**
|
||||
* \class QgsTextBufferSettings
|
||||
@ -252,7 +253,8 @@ class CORE_EXPORT QgsTextBackgroundSettings
|
||||
ShapeSquare, //!< Square - buffered sizes only
|
||||
ShapeEllipse, //!< Ellipse
|
||||
ShapeCircle, //!< Circle
|
||||
ShapeSVG //!< SVG file
|
||||
ShapeSVG, //!< SVG file
|
||||
ShapeMarkerSymbol, //!< Marker symbol
|
||||
};
|
||||
|
||||
/**
|
||||
@ -327,6 +329,24 @@ class CORE_EXPORT QgsTextBackgroundSettings
|
||||
*/
|
||||
void setSvgFile( const QString &file );
|
||||
|
||||
/**
|
||||
* Returns the marker symbol to be rendered in the background. Ownership remains with
|
||||
* the background settings.
|
||||
* \note This is only used when the type() is QgsTextBackgroundSettings::ShapeMarkerSymbol.
|
||||
* \see setMarkerSymbol()
|
||||
* \since QGIS 3.10
|
||||
*/
|
||||
QgsMarkerSymbol *markerSymbol() const;
|
||||
|
||||
/**
|
||||
* Sets the current marker \a symbol for the background shape. Ownership is transferred
|
||||
* to the background settings.
|
||||
* \note This is only used when the type() is QgsTextBackgroundSettings::ShapeMarkerSymbol.
|
||||
* \see markerSymbol()
|
||||
* \since QGIS 3.10
|
||||
*/
|
||||
void setMarkerSymbol( QgsMarkerSymbol *symbol SIP_TRANSFER );
|
||||
|
||||
/**
|
||||
* Returns the method used to determine the size of the background shape (e.g., fixed size or buffer
|
||||
* around text).
|
||||
|
@ -64,11 +64,6 @@ class QgsTextBufferSettingsPrivate : public QSharedData
|
||||
{
|
||||
}
|
||||
|
||||
~QgsTextBufferSettingsPrivate()
|
||||
{
|
||||
delete paintEffect;
|
||||
}
|
||||
|
||||
bool enabled = false;
|
||||
double size = 1;
|
||||
QgsUnitTypes::RenderUnit sizeUnit = QgsUnitTypes::RenderMillimeters;
|
||||
@ -78,7 +73,7 @@ class QgsTextBufferSettingsPrivate : public QSharedData
|
||||
bool fillBufferInterior = false;
|
||||
Qt::PenJoinStyle joinStyle = Qt::RoundJoin;
|
||||
QPainter::CompositionMode blendMode = QPainter::CompositionMode_SourceOver;
|
||||
QgsPaintEffect *paintEffect = nullptr;
|
||||
std::unique_ptr< QgsPaintEffect > paintEffect;
|
||||
};
|
||||
|
||||
|
||||
@ -121,14 +116,10 @@ class QgsTextBackgroundSettingsPrivate : public QSharedData
|
||||
, strokeWidthMapUnitScale( other.strokeWidthMapUnitScale )
|
||||
, joinStyle( other.joinStyle )
|
||||
, paintEffect( other.paintEffect ? other.paintEffect->clone() : nullptr )
|
||||
, markerSymbol( other.markerSymbol ? other.markerSymbol->clone() : nullptr )
|
||||
{
|
||||
}
|
||||
|
||||
~QgsTextBackgroundSettingsPrivate()
|
||||
{
|
||||
delete paintEffect;
|
||||
}
|
||||
|
||||
bool enabled = false;
|
||||
QgsTextBackgroundSettings::ShapeType type = QgsTextBackgroundSettings::ShapeRectangle;
|
||||
QString svgFile; //!< Absolute path to SVG file
|
||||
@ -152,7 +143,8 @@ class QgsTextBackgroundSettingsPrivate : public QSharedData
|
||||
QgsUnitTypes::RenderUnit strokeWidthUnits = QgsUnitTypes::RenderMillimeters;
|
||||
QgsMapUnitScale strokeWidthMapUnitScale;
|
||||
Qt::PenJoinStyle joinStyle = Qt::BevelJoin;
|
||||
QgsPaintEffect *paintEffect = nullptr;
|
||||
std::unique_ptr< QgsPaintEffect > paintEffect;
|
||||
std::unique_ptr< QgsMarkerSymbol > markerSymbol;
|
||||
};
|
||||
|
||||
|
||||
|
@ -165,6 +165,16 @@ std::unique_ptr<QgsMarkerSymbolLayer> backgroundToMarkerLayer( const QgsTextBack
|
||||
layer.reset( svg );
|
||||
break;
|
||||
}
|
||||
case QgsTextBackgroundSettings::ShapeMarkerSymbol:
|
||||
{
|
||||
// just grab the first layer and hope for the best
|
||||
if ( settings.markerSymbol() && settings.markerSymbol()->symbolLayerCount() > 0 )
|
||||
{
|
||||
layer.reset( static_cast< QgsMarkerSymbolLayer * >( settings.markerSymbol()->symbolLayer( 0 )->clone() ) );
|
||||
break;
|
||||
}
|
||||
FALLTHROUGH // not set, just go with the default
|
||||
}
|
||||
case QgsTextBackgroundSettings::ShapeCircle:
|
||||
case QgsTextBackgroundSettings::ShapeEllipse:
|
||||
case QgsTextBackgroundSettings::ShapeRectangle:
|
||||
@ -184,6 +194,7 @@ std::unique_ptr<QgsMarkerSymbolLayer> backgroundToMarkerLayer( const QgsTextBack
|
||||
shape = QgsSimpleMarkerSymbolLayerBase::Square;
|
||||
break;
|
||||
case QgsTextBackgroundSettings::ShapeSVG:
|
||||
case QgsTextBackgroundSettings::ShapeMarkerSymbol:
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -339,7 +339,7 @@ void QgsVectorLayerLabelProvider::drawLabel( QgsRenderContext &context, pal::Lab
|
||||
{
|
||||
QgsTextFormat format = tmpLyr.format();
|
||||
|
||||
if ( tmpLyr.format().background().enabled() )
|
||||
if ( tmpLyr.format().background().enabled() && tmpLyr.format().background().type() != QgsTextBackgroundSettings::ShapeMarkerSymbol ) // background shadows not compatible with marker symbol backgrounds
|
||||
{
|
||||
format.shadow().setShadowPlacement( QgsTextShadowSettings::ShadowShape );
|
||||
}
|
||||
|
@ -126,6 +126,7 @@ void QgsLabelingGui::setLayer( QgsMapLayer *mapLayer )
|
||||
mLayer = layer;
|
||||
|
||||
mTextFormatsListWidget->setLayerType( mLayer ? mLayer->geometryType() : mGeomType );
|
||||
mBackgroundSymbolButton->setLayer( mLayer );
|
||||
|
||||
// load labeling settings from layer
|
||||
updateGeometryTypeBasedWidgets();
|
||||
|
@ -27,7 +27,7 @@
|
||||
|
||||
///@cond PRIVATE
|
||||
|
||||
class GUI_EXPORT QgsLabelingGui : public QgsTextFormatWidget, private QgsExpressionContextGenerator
|
||||
class GUI_EXPORT QgsLabelingGui : public QgsTextFormatWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
|
@ -110,6 +110,7 @@ void QgsSymbolButton::showSettingsDialog()
|
||||
if ( panel && panel->dockMode() )
|
||||
{
|
||||
QgsSymbolSelectorWidget *d = new QgsSymbolSelectorWidget( newSymbol, QgsStyle::defaultStyle(), mLayer, nullptr );
|
||||
d->setPanelTitle( mDialogTitle );
|
||||
d->setContext( symbolContext );
|
||||
connect( d, &QgsPanelWidget::widgetChanged, this, &QgsSymbolButton::updateSymbolFromWidget );
|
||||
connect( d, &QgsPanelWidget::panelAccepted, this, &QgsSymbolButton::cleanUpSymbolSelector );
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "qgseffectstack.h"
|
||||
#include "qgspainteffectregistry.h"
|
||||
#include "qgsstylesavedialog.h"
|
||||
#include "qgsexpressioncontextutils.h"
|
||||
|
||||
#include <QButtonGroup>
|
||||
#include <QMessageBox>
|
||||
@ -160,6 +161,20 @@ void QgsTextFormatWidget::initWidget()
|
||||
mOffsetTypeComboBox->addItem( tr( "From point" ), QgsPalLayerSettings::FromPoint );
|
||||
mOffsetTypeComboBox->addItem( tr( "From symbol bounds" ), QgsPalLayerSettings::FromSymbolBounds );
|
||||
|
||||
mShapeTypeCmbBx->addItem( tr( "Rectangle" ), QgsTextBackgroundSettings::ShapeRectangle );
|
||||
mShapeTypeCmbBx->addItem( tr( "Square" ), QgsTextBackgroundSettings::ShapeSquare );
|
||||
mShapeTypeCmbBx->addItem( tr( "Ellipse" ), QgsTextBackgroundSettings::ShapeEllipse );
|
||||
mShapeTypeCmbBx->addItem( tr( "Circle" ), QgsTextBackgroundSettings::ShapeCircle );
|
||||
mShapeTypeCmbBx->addItem( tr( "SVG" ), QgsTextBackgroundSettings::ShapeSVG );
|
||||
mShapeTypeCmbBx->addItem( tr( "Marker Symbol" ), QgsTextBackgroundSettings::ShapeMarkerSymbol );
|
||||
|
||||
updateAvailableShadowPositions();
|
||||
|
||||
mBackgroundSymbolButton->setSymbolType( QgsSymbol::Marker );
|
||||
mBackgroundSymbolButton->setDialogTitle( tr( "Background Symbol" ) );
|
||||
mBackgroundSymbolButton->registerExpressionContextGenerator( this );
|
||||
mBackgroundSymbolButton->setMapCanvas( mMapCanvas );
|
||||
|
||||
mCharDlg = new QgsCharacterSelectorDialog( this );
|
||||
|
||||
mRefFont = lblFontPreview->font();
|
||||
@ -522,7 +537,8 @@ void QgsTextFormatWidget::initWidget()
|
||||
<< mGeometryGeneratorGroupBox
|
||||
<< mGeometryGenerator
|
||||
<< mGeometryGeneratorType
|
||||
<< mLinePlacementFlagsDDBtn;
|
||||
<< mLinePlacementFlagsDDBtn
|
||||
<< mBackgroundSymbolButton;
|
||||
connectValueChanged( widgets, SLOT( updatePreview() ) );
|
||||
|
||||
connect( mQuadrantBtnGrp, static_cast<void ( QButtonGroup::* )( int )>( &QButtonGroup::buttonClicked ), this, &QgsTextFormatWidget::updatePreview );
|
||||
@ -610,6 +626,10 @@ void QgsTextFormatWidget::connectValueChanged( const QList<QWidget *> &widgets,
|
||||
{
|
||||
connect( w, SIGNAL( changed() ), this, slot );
|
||||
}
|
||||
else if ( QgsSymbolButton *w = qobject_cast<QgsSymbolButton *>( widget ) )
|
||||
{
|
||||
connect( w, SIGNAL( changed() ), this, slot );
|
||||
}
|
||||
else if ( QgsFieldExpressionWidget *w = qobject_cast< QgsFieldExpressionWidget *>( widget ) )
|
||||
{
|
||||
connect( w, SIGNAL( fieldChanged( QString ) ), this, slot );
|
||||
@ -732,8 +752,9 @@ void QgsTextFormatWidget::updateWidgetForFormat( const QgsTextFormat &format )
|
||||
// shape background
|
||||
mShapeDrawChkBx->setChecked( background.enabled() );
|
||||
mShapeTypeCmbBx->blockSignals( true );
|
||||
mShapeTypeCmbBx->setCurrentIndex( background.type() );
|
||||
mShapeTypeCmbBx->setCurrentIndex( mShapeTypeCmbBx->findData( background.type() ) );
|
||||
mShapeTypeCmbBx->blockSignals( false );
|
||||
updateAvailableShadowPositions();
|
||||
mShapeSVGPathLineEdit->setText( background.svgFile() );
|
||||
|
||||
mShapeSizeCmbBx->setCurrentIndex( background.sizeType() );
|
||||
@ -776,9 +797,11 @@ void QgsTextFormatWidget::updateWidgetForFormat( const QgsTextFormat &format )
|
||||
}
|
||||
mBackgroundEffectWidget->setPaintEffect( mBackgroundEffect.get() );
|
||||
|
||||
mBackgroundSymbolButton->setSymbol( background.markerSymbol() ? background.markerSymbol()->clone() : QgsSymbol::defaultSymbol( QgsWkbTypes::PointGeometry ) );
|
||||
|
||||
// drop shadow
|
||||
mShadowDrawChkBx->setChecked( shadow.enabled() );
|
||||
mShadowUnderCmbBx->setCurrentIndex( shadow.shadowPlacement() );
|
||||
mShadowUnderCmbBx->setCurrentIndex( mShadowUnderCmbBx->findData( shadow.shadowPlacement() ) );
|
||||
mShadowOffsetAngleSpnBx->setValue( shadow.offsetAngle() );
|
||||
mShadowOffsetSpnBx->setValue( shadow.offsetDistance() );
|
||||
mShadowOffsetUnitWidget->setUnit( shadow.offsetUnit() );
|
||||
@ -842,7 +865,7 @@ QgsTextFormat QgsTextFormatWidget::format() const
|
||||
// shape background
|
||||
QgsTextBackgroundSettings background;
|
||||
background.setEnabled( mShapeDrawChkBx->isChecked() );
|
||||
background.setType( ( QgsTextBackgroundSettings::ShapeType )mShapeTypeCmbBx->currentIndex() );
|
||||
background.setType( static_cast< QgsTextBackgroundSettings::ShapeType >( mShapeTypeCmbBx->currentData().toInt() ) );
|
||||
background.setSvgFile( mShapeSVGPathLineEdit->text() );
|
||||
background.setSizeType( ( QgsTextBackgroundSettings::SizeType )mShapeSizeCmbBx->currentIndex() );
|
||||
background.setSize( QSizeF( mShapeSizeXSpnBx->value(), mShapeSizeYSpnBx->value() ) );
|
||||
@ -869,12 +892,13 @@ QgsTextFormat QgsTextFormatWidget::format() const
|
||||
background.setPaintEffect( mBackgroundEffect->clone() );
|
||||
else
|
||||
background.setPaintEffect( nullptr );
|
||||
background.setMarkerSymbol( mBackgroundSymbolButton->clonedSymbol< QgsMarkerSymbol >() );
|
||||
format.setBackground( background );
|
||||
|
||||
// drop shadow
|
||||
QgsTextShadowSettings shadow;
|
||||
shadow.setEnabled( mShadowDrawChkBx->isChecked() );
|
||||
shadow.setShadowPlacement( ( QgsTextShadowSettings::ShadowPlacement )mShadowUnderCmbBx->currentIndex() );
|
||||
shadow.setShadowPlacement( static_cast< QgsTextShadowSettings::ShadowPlacement >( mShadowUnderCmbBx->currentData().toInt() ) );
|
||||
shadow.setOffsetAngle( mShadowOffsetAngleSpnBx->value() );
|
||||
shadow.setOffsetDistance( mShadowOffsetSpnBx->value() );
|
||||
shadow.setOffsetUnit( mShadowOffsetUnitWidget->unit() );
|
||||
@ -1232,22 +1256,25 @@ void QgsTextFormatWidget::mCoordYDDBtn_activated( bool active )
|
||||
}
|
||||
}
|
||||
|
||||
void QgsTextFormatWidget::mShapeTypeCmbBx_currentIndexChanged( int index )
|
||||
void QgsTextFormatWidget::mShapeTypeCmbBx_currentIndexChanged( int )
|
||||
{
|
||||
// shape background
|
||||
bool isRect = ( ( QgsTextBackgroundSettings::ShapeType )index == QgsTextBackgroundSettings::ShapeRectangle
|
||||
|| ( QgsTextBackgroundSettings::ShapeType )index == QgsTextBackgroundSettings::ShapeSquare );
|
||||
bool isSVG = ( ( QgsTextBackgroundSettings::ShapeType )index == QgsTextBackgroundSettings::ShapeSVG );
|
||||
QgsTextBackgroundSettings::ShapeType type = static_cast< QgsTextBackgroundSettings::ShapeType >( mShapeTypeCmbBx->currentData().toInt() );
|
||||
const bool isRect = type == QgsTextBackgroundSettings::ShapeRectangle || type == QgsTextBackgroundSettings::ShapeSquare;
|
||||
const bool isSVG = type == QgsTextBackgroundSettings::ShapeSVG;
|
||||
const bool isMarker = type == QgsTextBackgroundSettings::ShapeMarkerSymbol;
|
||||
|
||||
showBackgroundPenStyle( isRect );
|
||||
showBackgroundRadius( isRect );
|
||||
|
||||
mShapeSVGPathFrame->setVisible( isSVG );
|
||||
mBackgroundSymbolButton->setVisible( isMarker );
|
||||
|
||||
// symbology SVG renderer only supports size^2 scaling, so we only use the x size spinbox
|
||||
mShapeSizeYLabel->setVisible( !isSVG );
|
||||
mShapeSizeYSpnBx->setVisible( !isSVG );
|
||||
mShapeSizeYDDBtn->setVisible( !isSVG && mWidgetMode == Labeling );
|
||||
mShapeSizeXLabel->setText( tr( "Size%1" ).arg( !isSVG ? tr( " X" ) : QString() ) );
|
||||
mShapeSizeYLabel->setVisible( !isSVG && !isMarker );
|
||||
mShapeSizeYSpnBx->setVisible( !isSVG && !isMarker );
|
||||
mShapeSizeYDDBtn->setVisible( !isSVG && !isMarker && mWidgetMode == Labeling );
|
||||
mShapeSizeXLabel->setText( tr( "Size%1" ).arg( !isSVG && !isMarker ? tr( " X" ) : QString() ) );
|
||||
|
||||
// SVG parameter setting doesn't support color's alpha component yet
|
||||
mShapeFillColorBtn->setAllowOpacity( !isSVG );
|
||||
@ -1263,21 +1290,23 @@ void QgsTextFormatWidget::mShapeTypeCmbBx_currentIndexChanged( int index )
|
||||
}
|
||||
else
|
||||
{
|
||||
mShapeFillColorLabel->setEnabled( true );
|
||||
mShapeFillColorBtn->setEnabled( true );
|
||||
mShapeFillColorDDBtn->setEnabled( true );
|
||||
mShapeStrokeColorLabel->setEnabled( true );
|
||||
mShapeStrokeColorBtn->setEnabled( true );
|
||||
mShapeStrokeColorDDBtn->setEnabled( true );
|
||||
mShapeStrokeWidthLabel->setEnabled( true );
|
||||
mShapeStrokeWidthSpnBx->setEnabled( true );
|
||||
mShapeStrokeWidthDDBtn->setEnabled( true );
|
||||
mShapeFillColorLabel->setEnabled( !isMarker );
|
||||
mShapeFillColorBtn->setEnabled( !isMarker );
|
||||
mShapeFillColorDDBtn->setEnabled( !isMarker );
|
||||
mShapeStrokeColorLabel->setEnabled( !isMarker );
|
||||
mShapeStrokeColorBtn->setEnabled( !isMarker );
|
||||
mShapeStrokeColorDDBtn->setEnabled( !isMarker );
|
||||
mShapeStrokeWidthLabel->setEnabled( !isMarker );
|
||||
mShapeStrokeWidthSpnBx->setEnabled( !isMarker );
|
||||
mShapeStrokeWidthDDBtn->setEnabled( !isMarker );
|
||||
}
|
||||
// TODO: fix overriding SVG symbol's stroke width units in QgsSvgCache
|
||||
// currently broken, fall back to symbol units only
|
||||
mShapeStrokeWidthUnitWidget->setVisible( !isSVG );
|
||||
mShapeStrokeWidthUnitWidget->setVisible( !isSVG && !isMarker );
|
||||
mShapeSVGUnitsLabel->setVisible( isSVG );
|
||||
mShapeStrokeUnitsDDBtn->setEnabled( !isSVG );
|
||||
mShapeStrokeUnitsDDBtn->setEnabled( !isSVG && !isMarker );
|
||||
|
||||
updateAvailableShadowPositions();
|
||||
}
|
||||
|
||||
void QgsTextFormatWidget::mShapeSVGPathLineEdit_textChanged( const QString &text )
|
||||
@ -1376,6 +1405,28 @@ void QgsTextFormatWidget::updateSvgWidgets( const QString &svgPath )
|
||||
mShapeSVGUnitsLabel->setEnabled( validSVG && strokeWidthParam );
|
||||
}
|
||||
|
||||
void QgsTextFormatWidget::updateAvailableShadowPositions()
|
||||
{
|
||||
if ( mShadowUnderCmbBx->count() == 0
|
||||
|| ( mShadowUnderCmbBx->findData( QgsTextShadowSettings::ShadowShape ) > -1 && mShapeTypeCmbBx->currentData().toInt() == QgsTextBackgroundSettings::ShapeMarkerSymbol )
|
||||
|| ( mShadowUnderCmbBx->findData( QgsTextShadowSettings::ShadowShape ) == -1 && mShapeTypeCmbBx->currentData().toInt() != QgsTextBackgroundSettings::ShapeMarkerSymbol ) )
|
||||
{
|
||||
// showing invalid choices, have to rebuild the list
|
||||
QgsTextShadowSettings::ShadowPlacement currentPlacement = static_cast< QgsTextShadowSettings::ShadowPlacement >( mShadowUnderCmbBx->currentData().toInt() );
|
||||
mShadowUnderCmbBx->clear();
|
||||
|
||||
mShadowUnderCmbBx->addItem( tr( "Lowest label component" ), QgsTextShadowSettings::ShadowLowest );
|
||||
mShadowUnderCmbBx->addItem( tr( "Text" ), QgsTextShadowSettings::ShadowText );
|
||||
mShadowUnderCmbBx->addItem( tr( "Buffer" ), QgsTextShadowSettings::ShadowBuffer );
|
||||
if ( mShapeTypeCmbBx->currentData().toInt() != QgsTextBackgroundSettings::ShapeMarkerSymbol )
|
||||
mShadowUnderCmbBx->addItem( tr( "Background" ), QgsTextShadowSettings::ShadowShape ); // not supported for marker symbol background shapes
|
||||
|
||||
mShadowUnderCmbBx->setCurrentIndex( mShadowUnderCmbBx->findData( currentPlacement ) );
|
||||
if ( mShadowUnderCmbBx->currentIndex() == -1 )
|
||||
mShadowUnderCmbBx->setCurrentIndex( 0 );
|
||||
}
|
||||
}
|
||||
|
||||
void QgsTextFormatWidget::setFormatFromStyle( const QString &name, QgsStyle::StyleEntity type )
|
||||
{
|
||||
switch ( type )
|
||||
@ -1587,6 +1638,18 @@ void QgsTextFormatWidget::enableDataDefinedAlignment( bool enable )
|
||||
mCoordAlignmentFrame->setEnabled( enable );
|
||||
}
|
||||
|
||||
QgsExpressionContext QgsTextFormatWidget::createExpressionContext() const
|
||||
{
|
||||
QgsExpressionContext expContext;
|
||||
expContext << QgsExpressionContextUtils::globalScope()
|
||||
<< QgsExpressionContextUtils::projectScope( QgsProject::instance() )
|
||||
<< QgsExpressionContextUtils::atlasScope( nullptr );
|
||||
if ( mMapCanvas )
|
||||
expContext << QgsExpressionContextUtils::mapSettingsScope( mMapCanvas->mapSettings() );
|
||||
|
||||
return expContext;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// QgsTextFormatDialog
|
||||
|
@ -46,7 +46,7 @@ class QgsCharacterSelectorDialog;
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
|
||||
class GUI_EXPORT QgsTextFormatWidget : public QWidget, protected Ui::QgsTextFormatWidgetBase
|
||||
class GUI_EXPORT QgsTextFormatWidget : public QWidget, public QgsExpressionContextGenerator, protected Ui::QgsTextFormatWidgetBase
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY( QgsTextFormat format READ format )
|
||||
@ -122,6 +122,8 @@ class GUI_EXPORT QgsTextFormatWidget : public QWidget, protected Ui::QgsTextForm
|
||||
*/
|
||||
void enableDataDefinedAlignment( bool enable );
|
||||
|
||||
QgsExpressionContext createExpressionContext() const override;
|
||||
|
||||
//! Text substitution list
|
||||
QgsStringReplacementCollection mSubstitutions;
|
||||
//! Quadrant button group
|
||||
@ -222,6 +224,7 @@ class GUI_EXPORT QgsTextFormatWidget : public QWidget, protected Ui::QgsTextForm
|
||||
void updatePreview();
|
||||
void scrollPreview();
|
||||
void updateSvgWidgets( const QString &svgPath );
|
||||
void updateAvailableShadowPositions();
|
||||
|
||||
};
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -18,7 +18,8 @@ from qgis.core import (QgsTextBufferSettings,
|
||||
QgsTextFormat,
|
||||
QgsUnitTypes,
|
||||
QgsMapUnitScale,
|
||||
QgsBlurEffect)
|
||||
QgsBlurEffect,
|
||||
QgsMarkerSymbol)
|
||||
from qgis.gui import (QgsTextFormatWidget, QgsTextFormatDialog)
|
||||
from qgis.PyQt.QtGui import (QColor, QPainter)
|
||||
from qgis.PyQt.QtCore import (Qt, QSizeF, QPointF)
|
||||
@ -84,6 +85,11 @@ class PyQgsTextFormatWidget(unittest.TestCase):
|
||||
s.setStrokeWidthUnit(QgsUnitTypes.RenderMapUnits)
|
||||
s.setStrokeWidthMapUnitScale(QgsMapUnitScale(QgsMapUnitScale(25, 26)))
|
||||
s.setPaintEffect(QgsBlurEffect.create({'blur_level': '6.0', 'blur_unit': QgsUnitTypes.encodeUnit(QgsUnitTypes.RenderMillimeters), 'enabled': '1'}))
|
||||
|
||||
marker = QgsMarkerSymbol()
|
||||
marker.setColor(QColor(100, 112, 134))
|
||||
s.setMarkerSymbol(marker)
|
||||
|
||||
return s
|
||||
|
||||
def checkBackgroundSettings(self, s):
|
||||
|
@ -26,7 +26,8 @@ from qgis.core import (QgsTextBufferSettings,
|
||||
QgsRenderContext,
|
||||
QgsRectangle,
|
||||
QgsRenderChecker,
|
||||
QgsBlurEffect)
|
||||
QgsBlurEffect,
|
||||
QgsMarkerSymbol)
|
||||
from qgis.PyQt.QtGui import (QColor, QPainter, QFont, QImage, QBrush, QPen, QFontMetricsF)
|
||||
from qgis.PyQt.QtCore import (Qt, QSizeF, QPointF, QRectF, QDir, QSize)
|
||||
from qgis.PyQt.QtXml import QDomDocument
|
||||
@ -135,6 +136,11 @@ class PyQgsTextRenderer(unittest.TestCase):
|
||||
s.setStrokeWidth(7)
|
||||
s.setStrokeWidthUnit(QgsUnitTypes.RenderPoints)
|
||||
s.setStrokeWidthMapUnitScale(QgsMapUnitScale(QgsMapUnitScale(25, 26)))
|
||||
|
||||
marker = QgsMarkerSymbol()
|
||||
marker.setColor(QColor(100, 112, 134))
|
||||
s.setMarkerSymbol(marker)
|
||||
|
||||
return s
|
||||
|
||||
def checkBackgroundSettings(self, s):
|
||||
@ -162,6 +168,7 @@ class PyQgsTextRenderer(unittest.TestCase):
|
||||
self.assertEqual(s.strokeWidth(), 7)
|
||||
self.assertEqual(s.strokeWidthUnit(), QgsUnitTypes.RenderPoints)
|
||||
self.assertEqual(s.strokeWidthMapUnitScale(), QgsMapUnitScale(25, 26))
|
||||
self.assertEqual(s.markerSymbol().color().name(), '#647086')
|
||||
|
||||
def testBackgroundGettersSetters(self):
|
||||
s = self.createBackgroundSettings()
|
||||
@ -929,6 +936,75 @@ class PyQgsTextRenderer(unittest.TestCase):
|
||||
assert self.checkRender(format, 'background_svg_buffer_mm', QgsTextRenderer.Background,
|
||||
rect=QRectF(100, 100, 100, 100))
|
||||
|
||||
def testDrawBackgroundMarkerFixedPixels(self):
|
||||
format = QgsTextFormat()
|
||||
format.setFont(getTestFont('bold'))
|
||||
format.background().setEnabled(True)
|
||||
format.background().setMarkerSymbol(QgsMarkerSymbol.createSimple({'color': '#ffffff', 'size': '3', 'outline_color': 'red', 'outline_width': '3'}))
|
||||
format.background().setType(QgsTextBackgroundSettings.ShapeMarkerSymbol)
|
||||
format.background().setSize(QSizeF(60, 80))
|
||||
format.background().setSizeType(QgsTextBackgroundSettings.SizeFixed)
|
||||
format.background().setSizeUnit(QgsUnitTypes.RenderPixels)
|
||||
assert self.checkRender(format, 'background_marker_fixed_pixels', QgsTextRenderer.Background)
|
||||
|
||||
def testDrawBackgroundMarkerFixedMapUnits(self):
|
||||
format = QgsTextFormat()
|
||||
format.setFont(getTestFont('bold'))
|
||||
format.background().setEnabled(True)
|
||||
format.background().setMarkerSymbol(QgsMarkerSymbol.createSimple({'color': '#ffffff', 'size': '3', 'outline_color': 'red', 'outline_width': '3'}))
|
||||
format.background().setType(QgsTextBackgroundSettings.ShapeMarkerSymbol)
|
||||
format.background().setSize(QSizeF(20, 20))
|
||||
format.background().setSizeType(QgsTextBackgroundSettings.SizeFixed)
|
||||
format.background().setSizeUnit(QgsUnitTypes.RenderMapUnits)
|
||||
assert self.checkRender(format, 'background_marker_fixed_mapunits', QgsTextRenderer.Background)
|
||||
|
||||
def testDrawBackgroundMarkerFixedMM(self):
|
||||
format = QgsTextFormat()
|
||||
format.setFont(getTestFont('bold'))
|
||||
format.background().setEnabled(True)
|
||||
format.background().setMarkerSymbol(QgsMarkerSymbol.createSimple({'color': '#ffffff', 'size': '3', 'outline_color': 'red', 'outline_width': '3'}))
|
||||
format.background().setType(QgsTextBackgroundSettings.ShapeMarkerSymbol)
|
||||
format.background().setSize(QSizeF(30, 30))
|
||||
format.background().setSizeType(QgsTextBackgroundSettings.SizeFixed)
|
||||
format.background().setSizeUnit(QgsUnitTypes.RenderMillimeters)
|
||||
assert self.checkRender(format, 'background_marker_fixed_mm', QgsTextRenderer.Background)
|
||||
|
||||
def testDrawBackgroundMarkerBufferPixels(self):
|
||||
format = QgsTextFormat()
|
||||
format.setFont(getTestFont('bold'))
|
||||
format.background().setEnabled(True)
|
||||
format.background().setMarkerSymbol(QgsMarkerSymbol.createSimple({'color': '#ffffff', 'size': '3', 'outline_color': 'red', 'outline_width': '3'}))
|
||||
format.background().setType(QgsTextBackgroundSettings.ShapeMarkerSymbol)
|
||||
format.background().setSize(QSizeF(30, 30))
|
||||
format.background().setSizeType(QgsTextBackgroundSettings.SizeBuffer)
|
||||
format.background().setSizeUnit(QgsUnitTypes.RenderPixels)
|
||||
assert self.checkRender(format, 'background_marker_buffer_pixels', QgsTextRenderer.Background,
|
||||
rect=QRectF(100, 100, 100, 100))
|
||||
|
||||
def testDrawBackgroundMarkerBufferMapUnits(self):
|
||||
format = QgsTextFormat()
|
||||
format.setFont(getTestFont('bold'))
|
||||
format.background().setEnabled(True)
|
||||
format.background().setMarkerSymbol(QgsMarkerSymbol.createSimple({'color': '#ffffff', 'size': '3', 'outline_color': 'red', 'outline_width': '3'}))
|
||||
format.background().setType(QgsTextBackgroundSettings.ShapeMarkerSymbol)
|
||||
format.background().setSize(QSizeF(4, 4))
|
||||
format.background().setSizeType(QgsTextBackgroundSettings.SizeBuffer)
|
||||
format.background().setSizeUnit(QgsUnitTypes.RenderMapUnits)
|
||||
assert self.checkRender(format, 'background_marker_buffer_mapunits', QgsTextRenderer.Background,
|
||||
rect=QRectF(100, 100, 100, 100))
|
||||
|
||||
def testDrawBackgroundMarkerBufferMM(self):
|
||||
format = QgsTextFormat()
|
||||
format.setFont(getTestFont('bold'))
|
||||
format.background().setEnabled(True)
|
||||
format.background().setMarkerSymbol(QgsMarkerSymbol.createSimple({'color': '#ffffff', 'size': '3', 'outline_color': 'red', 'outline_width': '3'}))
|
||||
format.background().setType(QgsTextBackgroundSettings.ShapeMarkerSymbol)
|
||||
format.background().setSize(QSizeF(10, 10))
|
||||
format.background().setSizeType(QgsTextBackgroundSettings.SizeBuffer)
|
||||
format.background().setSizeUnit(QgsUnitTypes.RenderMillimeters)
|
||||
assert self.checkRender(format, 'background_marker_buffer_mm', QgsTextRenderer.Background,
|
||||
rect=QRectF(100, 100, 100, 100))
|
||||
|
||||
def testDrawBackgroundRotationFixed(self):
|
||||
format = QgsTextFormat()
|
||||
format.setFont(getTestFont('bold'))
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.6 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.0 KiB |
Loading…
x
Reference in New Issue
Block a user