Early prototype of label callouts

This commit is contained in:
Nyall Dawson 2019-07-04 16:37:00 +10:00
parent 7833162ca0
commit fb1a610007
14 changed files with 1007 additions and 27 deletions

View File

@ -95,6 +95,7 @@ INCLUDE_DIRECTORIES(
${CMAKE_SOURCE_DIR}/src/core/3d
${CMAKE_SOURCE_DIR}/src/core/annotations
${CMAKE_SOURCE_DIR}/src/core/auth
${CMAKE_SOURCE_DIR}/src/core/callouts
${CMAKE_SOURCE_DIR}/src/core/expression
${CMAKE_SOURCE_DIR}/src/core/pal
${CMAKE_SOURCE_DIR}/src/core/diagram

View File

@ -0,0 +1,232 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/callouts/qgscallout.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsCallout
{
%Docstring
Abstract base class for callout renderers.
Implementations of QgsCallout are responsible for performing the actual render of
callouts, including determining the desired shape of the callout and using any
relevant symbology elements to render them.
.. versionadded:: 3.10
%End
%TypeHeaderCode
#include "qgscallout.h"
%End
public:
QgsCallout();
%Docstring
Constructor for QgsCallout.
%End
virtual ~QgsCallout();
virtual QString type() const = 0;
%Docstring
Returns a unique string representing the callout type.
%End
virtual QgsCallout *clone() const = 0 /Factory/;
%Docstring
Duplicates a callout by creating a deep copy of the callout.
Caller takes ownership of the returned object.
%End
virtual QVariantMap properties() const;
%Docstring
Returns the properties describing the callout encoded in a
string format.
Subclasses must ensure that they include the base class' properties()
in their returned value.
.. seealso:: :py:func:`readProperties`
.. seealso:: :py:func:`saveProperties`
%End
virtual void readProperties( const QVariantMap &props, const QgsReadWriteContext &context );
%Docstring
Reads a string map of an callout's properties and restores the callout
to the state described by the properties map.
Subclasses must ensure that they call the base class' readProperties()
method.
.. seealso:: :py:func:`properties`
%End
virtual bool saveProperties( QDomDocument &doc, QDomElement &element ) const;
%Docstring
Saves the current state of the callout to a DOM ``element``. The default
behavior is to save the properties string map returned by
properties().
:return: ``True`` if save was successful
.. seealso:: :py:func:`readProperties`
%End
virtual void restoreProperties( const QDomElement &element, const QgsReadWriteContext &context );
%Docstring
Restores the callout's properties from a DOM element.
The default behavior is the read the DOM contents and call readProperties() on the subclass.
.. seealso:: :py:func:`readProperties`
%End
virtual void startRender( QgsRenderContext &context );
%Docstring
Prepares the callout for rendering on the specified render ``context``.
.. warning::
This MUST be called prior to calling render() on the callout, and must always
be accompanied by a corresponding call to stopRender().
.. seealso:: :py:func:`stopRender`
%End
virtual void stopRender( QgsRenderContext &context );
%Docstring
Finalises the callout after a set of rendering operations on the specified render ``context``.
.. warning::
This MUST be called after to after render() operations on the callout, and must always
be accompanied by a corresponding prior call to startRender().
.. seealso:: :py:func:`startRender`
%End
virtual QSet< QString > referencedFields( const QgsRenderContext &context ) const;
%Docstring
Returns the set of attributes referenced by the callout. This includes attributes
required by any data defined properties associated with the callout.
.. warning::
This must only be called after a corresponding call to startRender() with
the same render ``context``.
%End
void render( QgsRenderContext &context, QRectF rect, const double angle, const QgsGeometry &anchor );
%Docstring
Renders the callout onto the specified render ``context``.
The ``rect`` argument gives the desired size and position of the body of the callout (e.g. the
actual label geometry). The ``angle`` argument specifies the rotation of the callout body
(in degrees clockwise from horizontal). It is assumed that angle rotation specified via ``angle``
is applied around the center of ``rect``.
The ``anchor`` argument dictates the geometry which the callout should connect to. Depending on the
callout subclass and anchor geometry type, the actual shape of the rendered callout may vary.
E.g. a subclass may prefer to attach to the centroid of the ``anchor``, while another subclass may
prefer to attach to the closest point on ``anchor`` instead.
Both ``rect`` and ``anchor`` must be specified in painter coordinates (i.e. pixels).
.. warning::
A prior call to startRender() must have been made before calling this method, and
after all render() operations are complete a call to stopRender() must be made.
%End
bool enabled() const;
%Docstring
Returns ``True`` if the the callout is enabled.
.. seealso:: :py:func:`setEnabled`
%End
void setEnabled( bool enabled );
%Docstring
Sets whether the callout is ``enabled``.
.. seealso:: :py:func:`enabled`
%End
protected:
virtual void draw( QgsRenderContext &context, QRectF rect, const double angle, const QgsGeometry &anchor ) = 0;
%Docstring
Performs the actual rendering of the callout implementation onto the specified render ``context``.
The ``rect`` argument gives the desired size and position of the body of the callout (e.g. the
actual label geometry). The ``angle`` argument specifies the rotation of the callout body
(in degrees clockwise from horizontal). It is assumed that angle rotation specified via ``angle``
is applied around the center of ``rect``.
The ``anchor`` argument dictates the geometry which the callout should connect to. Depending on the
callout subclass and anchor geometry type, the actual shape of the rendered callout may vary.
E.g. a subclass may prefer to attach to the centroid of the ``anchor``, while another subclass may
prefer to attach to the closest point on ``anchor`` instead.
Both ``rect`` and ``anchor`` are specified in painter coordinates (i.e. pixels).
%End
};
class QgsSimpleLineCallout : QgsCallout
{
%Docstring
A simple direct line callout style.
.. versionadded:: 3.10
%End
%TypeHeaderCode
#include "qgscallout.h"
%End
public:
QgsSimpleLineCallout();
QgsSimpleLineCallout( const QgsSimpleLineCallout &other );
virtual QString type() const;
virtual QgsSimpleLineCallout *clone() const;
virtual QVariantMap properties() const;
virtual void readProperties( const QVariantMap &props, const QgsReadWriteContext &context );
virtual void startRender( QgsRenderContext &context );
virtual void stopRender( QgsRenderContext &context );
virtual QSet< QString > referencedFields( const QgsRenderContext &context ) const;
QgsLineSymbol *lineSymbol();
void setLineSymbol( QgsLineSymbol *symbol /Transfer/ );
protected:
virtual void draw( QgsRenderContext &context, QRectF rect, const double angle, const QgsGeometry &anchor );
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/callouts/qgscallout.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -11,7 +11,6 @@
class QgsLabelPosition
{
@ -538,6 +537,28 @@ Sets the label text formatting settings, e.g., font settings, buffer settings, e
.. seealso:: :py:func:`format`
.. versionadded:: 3.0
%End
QgsCallout *callout() const;
%Docstring
Returns the label callout renderer, responsible for drawing label callouts.
Ownership is not transferred.
.. seealso:: :py:func:`setCallout`
.. versionadded:: 3.10
%End
void setCallout( QgsCallout *callout /Transfer/ );
%Docstring
Sets the label ``callout`` renderer, responsible for drawing label callouts.
Ownership of ``callout`` is transferred to the settings.
.. seealso:: :py:func:`callout`
.. versionadded:: 3.10
%End
static QPixmap labelSettingsPreviewPixmap( const QgsPalLayerSettings &settings, QSize size, const QString &previewText = QString(), int padding = 0 );

View File

@ -152,6 +152,7 @@
%Include auto_generated/auth/qgsauthconfig.sip
%Include auto_generated/auth/qgsauthmanager.sip
%Include auto_generated/auth/qgsauthmethod.sip
%Include auto_generated/callouts/qgscallout.sip
%Include auto_generated/diagram/qgsdiagram.sip
%Include auto_generated/diagram/qgspiediagram.sip
%Include auto_generated/diagram/qgstextdiagram.sip

View File

@ -17,6 +17,8 @@ SET(QGIS_CORE_SRCS
${CMAKE_SOURCE_DIR}/external/poly2tri/sweep/sweep_context.cc
${CMAKE_SOURCE_DIR}/external/poly2tri/sweep/sweep.cc
callouts/qgscallout.cpp
gps/qgsgpsconnection.cpp
gps/qgsgpsconnectionregistry.cpp
gps/qgsgpsdconnection.cpp
@ -1046,6 +1048,8 @@ SET(QGIS_CORE_HDRS
auth/qgsauthmethodmetadata.h
auth/qgsauthmethodregistry.h
callouts/qgscallout.h
diagram/qgsdiagram.h
diagram/qgspiediagram.h
diagram/qgstextdiagram.h

View File

@ -0,0 +1,227 @@
/***************************************************************************
qgscallout.cpp
----------------
begin : July 2019
copyright : (C) 2019 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 "qgscallout.h"
#include "qgsrendercontext.h"
#include "qgssymbol.h"
#include "qgslinesymbollayer.h"
#include "qgssymbollayerutils.h"
#include "qgsxmlutils.h"
#include <QPainter>
QVariantMap QgsCallout::properties() const
{
QVariantMap props;
props.insert( QStringLiteral( "enabled" ), mEnabled ? "1" : "0" );
return props;
}
void QgsCallout::readProperties( const QVariantMap &props, const QgsReadWriteContext & )
{
mEnabled = props.value( QStringLiteral( "enabled" ), QStringLiteral( "0" ) ).toInt();
}
bool QgsCallout::saveProperties( QDomDocument &doc, QDomElement &element ) const
{
if ( element.isNull() )
{
return false;
}
QDomElement calloutPropsElement = QgsXmlUtils::writeVariant( properties(), doc );
QDomElement calloutElement = doc.createElement( QStringLiteral( "callout" ) );
calloutElement.setAttribute( QStringLiteral( "type" ), type() );
calloutElement.appendChild( calloutPropsElement );
element.appendChild( calloutElement );
return true;
}
void QgsCallout::restoreProperties( const QDomElement &element, const QgsReadWriteContext &context )
{
const QVariantMap props = QgsXmlUtils::readVariant( element.firstChildElement() ).toMap();
readProperties( props, context );
}
void QgsCallout::startRender( QgsRenderContext & )
{
}
void QgsCallout::stopRender( QgsRenderContext & )
{
}
QSet<QString> QgsCallout::referencedFields( const QgsRenderContext & ) const
{
return QSet< QString >();
}
void QgsCallout::render( QgsRenderContext &context, QRectF rect, const double angle, const QgsGeometry &anchor )
{
if ( !mEnabled )
return;
#if 0 // for debugging
QPainter *painter = context.painter();
painter->save();
painter->setRenderHint( QPainter::Antialiasing, false );
painter->translate( rect.center() );
painter->rotate( -angle );
painter->setBrush( QColor( 255, 0, 0, 100 ) );
painter->setPen( QColor( 255, 0, 0, 150 ) );
painter->drawRect( rect.width() * -0.5, rect.height() * -0.5, rect.width(), rect.height() );
painter->restore();
painter->setBrush( QColor( 0, 255, 0, 100 ) );
painter->setPen( QColor( 0, 255, 0, 150 ) );
painter->drawRect( anchor.boundingBox( ).buffered( 30 ).toRectF() );
#endif
draw( context, rect, angle, anchor );
}
void QgsCallout::setEnabled( bool enabled )
{
mEnabled = enabled;
}
//
// QgsSimpleLineCallout
//
QgsSimpleLineCallout::QgsSimpleLineCallout()
{
mLineSymbol = qgis::make_unique< QgsLineSymbol >( QgsSymbolLayerList() << new QgsSimpleLineSymbolLayer( QColor( 60, 60, 60 ), .3 ) );
}
QgsSimpleLineCallout::QgsSimpleLineCallout( const QgsSimpleLineCallout &other )
: QgsCallout( other )
, mLineSymbol( other.mLineSymbol ? other.mLineSymbol->clone() : nullptr )
{
}
QgsSimpleLineCallout &QgsSimpleLineCallout::operator=( const QgsSimpleLineCallout &other )
{
mLineSymbol.reset( other.mLineSymbol ? other.mLineSymbol->clone() : nullptr );
}
QString QgsSimpleLineCallout::type() const
{
return QStringLiteral( "simple" );
}
QgsSimpleLineCallout *QgsSimpleLineCallout::clone() const
{
return new QgsSimpleLineCallout( *this );
}
QVariantMap QgsSimpleLineCallout::properties() const
{
QVariantMap props = QgsCallout::properties();
if ( mLineSymbol )
{
props[ QStringLiteral( "lineSymbol" ) ] = QgsSymbolLayerUtils::symbolProperties( mLineSymbol.get() );
}
return props;
}
void QgsSimpleLineCallout::readProperties( const QVariantMap &props, const QgsReadWriteContext &context )
{
QgsCallout::readProperties( props, context );
const QString lineSymbolDef = props.value( QStringLiteral( "lineSymbol" ) ).toString();
QDomDocument doc( QStringLiteral( "symbol" ) );
doc.setContent( lineSymbolDef );
QDomElement symbolElem = doc.firstChildElement( QStringLiteral( "symbol" ) );
std::unique_ptr< QgsLineSymbol > lineSymbol( QgsSymbolLayerUtils::loadSymbol< QgsLineSymbol >( symbolElem, context ) );
if ( lineSymbol )
mLineSymbol = std::move( lineSymbol );
}
void QgsSimpleLineCallout::startRender( QgsRenderContext &context )
{
QgsCallout::startRender( context );
if ( mLineSymbol )
mLineSymbol->startRender( context );
}
void QgsSimpleLineCallout::stopRender( QgsRenderContext &context )
{
QgsCallout::stopRender( context );
if ( mLineSymbol )
mLineSymbol->stopRender( context );
}
QSet<QString> QgsSimpleLineCallout::referencedFields( const QgsRenderContext &context ) const
{
QSet<QString> fields = QgsCallout::referencedFields( context );
if ( mLineSymbol )
fields.unite( mLineSymbol->usedAttributes( context ) );
return fields;
}
QgsLineSymbol *QgsSimpleLineCallout::lineSymbol()
{
return mLineSymbol.get();
}
void QgsSimpleLineCallout::setLineSymbol( QgsLineSymbol *symbol )
{
mLineSymbol.reset( symbol );
}
void QgsSimpleLineCallout::draw( QgsRenderContext &context, QRectF rect, const double, const QgsGeometry &anchor )
{
QgsGeometry label( QgsGeometry::fromRect( rect ) );
QgsGeometry line;
switch ( anchor.type() )
{
case QgsWkbTypes::PointGeometry:
line = label.shortestLine( anchor );
break;
case QgsWkbTypes::LineGeometry:
line = label.shortestLine( anchor );
break;
case QgsWkbTypes::PolygonGeometry:
if ( label.intersects( anchor ) )
return;
line = label.shortestLine( anchor.poleOfInaccessibility( std::max( anchor.boundingBox().width(), anchor.boundingBox().height() ) / 20.0 ) ); // really rough (but quick) pole of inaccessibility
break;
case QgsWkbTypes::NullGeometry:
case QgsWkbTypes::UnknownGeometry:
return; // shouldn't even get here..
}
if ( qgsDoubleNear( line.length(), 0 ) )
return;
mLineSymbol->renderPolyline( line.asQPolygonF(), nullptr, context );
}

View File

@ -0,0 +1,227 @@
/***************************************************************************
qgscallout.h
----------------
begin : July 2019
copyright : (C) 2019 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 QGSCALLOUT_H
#define QGSCALLOUT_H
#include "qgis_core.h"
#include "qgis_sip.h"
#include "qgsexpressioncontext.h"
#include <QString>
#include <QRectF>
#include <memory>
class QgsLineSymbol;
class QgsGeometry;
class QgsRenderContext;
/**
* \ingroup core
* \brief Abstract base class for callout renderers.
*
* Implementations of QgsCallout are responsible for performing the actual render of
* callouts, including determining the desired shape of the callout and using any
* relevant symbology elements to render them.
*
* \since QGIS 3.10
*/
class CORE_EXPORT QgsCallout
{
public:
/**
* Constructor for QgsCallout.
*/
QgsCallout() = default;
virtual ~QgsCallout() = default;
/**
* Returns a unique string representing the callout type.
*/
virtual QString type() const = 0;
/**
* Duplicates a callout by creating a deep copy of the callout.
*
* Caller takes ownership of the returned object.
*/
virtual QgsCallout *clone() const = 0 SIP_FACTORY;
/**
* Returns the properties describing the callout encoded in a
* string format.
*
* Subclasses must ensure that they include the base class' properties()
* in their returned value.
*
* \see readProperties()
* \see saveProperties()
*/
virtual QVariantMap properties() const;
/**
* Reads a string map of an callout's properties and restores the callout
* to the state described by the properties map.
*
* Subclasses must ensure that they call the base class' readProperties()
* method.
*
* \see properties()
*/
virtual void readProperties( const QVariantMap &props, const QgsReadWriteContext &context );
/**
* Saves the current state of the callout to a DOM \a element. The default
* behavior is to save the properties string map returned by
* properties().
* \returns TRUE if save was successful
* \see readProperties()
*/
virtual bool saveProperties( QDomDocument &doc, QDomElement &element ) const;
/**
* Restores the callout's properties from a DOM element.
*
* The default behavior is the read the DOM contents and call readProperties() on the subclass.
*
* \see readProperties()
*/
virtual void restoreProperties( const QDomElement &element, const QgsReadWriteContext &context );
/**
* Prepares the callout for rendering on the specified render \a context.
*
* \warning This MUST be called prior to calling render() on the callout, and must always
* be accompanied by a corresponding call to stopRender().
*
* \see stopRender()
*/
virtual void startRender( QgsRenderContext &context );
/**
* Finalises the callout after a set of rendering operations on the specified render \a context.
*
* \warning This MUST be called after to after render() operations on the callout, and must always
* be accompanied by a corresponding prior call to startRender().
*
* \see startRender()
*/
virtual void stopRender( QgsRenderContext &context );
/**
* Returns the set of attributes referenced by the callout. This includes attributes
* required by any data defined properties associated with the callout.
*
* \warning This must only be called after a corresponding call to startRender() with
* the same render \a context.
*/
virtual QSet< QString > referencedFields( const QgsRenderContext &context ) const;
/**
* Renders the callout onto the specified render \a context.
*
* The \a rect argument gives the desired size and position of the body of the callout (e.g. the
* actual label geometry). The \a angle argument specifies the rotation of the callout body
* (in degrees clockwise from horizontal). It is assumed that angle rotation specified via \a angle
* is applied around the center of \a rect.
*
* The \a anchor argument dictates the geometry which the callout should connect to. Depending on the
* callout subclass and anchor geometry type, the actual shape of the rendered callout may vary.
* E.g. a subclass may prefer to attach to the centroid of the \a anchor, while another subclass may
* prefer to attach to the closest point on \a anchor instead.
*
* Both \a rect and \a anchor must be specified in painter coordinates (i.e. pixels).
*
* \warning A prior call to startRender() must have been made before calling this method, and
* after all render() operations are complete a call to stopRender() must be made.
*/
void render( QgsRenderContext &context, QRectF rect, const double angle, const QgsGeometry &anchor );
/**
* Returns TRUE if the the callout is enabled.
* \see setEnabled()
*/
bool enabled() const { return mEnabled; }
/**
* Sets whether the callout is \a enabled.
* \see enabled()
*/
void setEnabled( bool enabled );
protected:
/**
* Performs the actual rendering of the callout implementation onto the specified render \a context.
*
* The \a rect argument gives the desired size and position of the body of the callout (e.g. the
* actual label geometry). The \a angle argument specifies the rotation of the callout body
* (in degrees clockwise from horizontal). It is assumed that angle rotation specified via \a angle
* is applied around the center of \a rect.
*
* The \a anchor argument dictates the geometry which the callout should connect to. Depending on the
* callout subclass and anchor geometry type, the actual shape of the rendered callout may vary.
* E.g. a subclass may prefer to attach to the centroid of the \a anchor, while another subclass may
* prefer to attach to the closest point on \a anchor instead.
*
* Both \a rect and \a anchor are specified in painter coordinates (i.e. pixels).
*/
virtual void draw( QgsRenderContext &context, QRectF rect, const double angle, const QgsGeometry &anchor ) = 0;
private:
bool mEnabled = true;
};
/**
* \ingroup core
* \brief A simple direct line callout style.
*
* \since QGIS 3.10
*/
class CORE_EXPORT QgsSimpleLineCallout : public QgsCallout
{
public:
QgsSimpleLineCallout();
QgsSimpleLineCallout( const QgsSimpleLineCallout &other );
QgsSimpleLineCallout &operator=( const QgsSimpleLineCallout & );
QString type() const override;
QgsSimpleLineCallout *clone() const override;
QVariantMap properties() const override;
void readProperties( const QVariantMap &props, const QgsReadWriteContext &context ) override;
void startRender( QgsRenderContext &context ) override;
void stopRender( QgsRenderContext &context ) override;
QSet< QString > referencedFields( const QgsRenderContext &context ) const override;
QgsLineSymbol *lineSymbol();
void setLineSymbol( QgsLineSymbol *symbol SIP_TRANSFER );
protected:
void draw( QgsRenderContext &context, QRectF rect, const double angle, const QgsGeometry &anchor ) override;
private:
std::unique_ptr< QgsLineSymbol > mLineSymbol;
};
#endif // QGSCALLOUT_H

View File

@ -63,6 +63,7 @@
#include "qgscurvepolygon.h"
#include "qgsmessagelog.h"
#include "qgsgeometrycollection.h"
#include "callouts/qgscallout.h"
#include <QMessageBox>
using namespace pal;
@ -303,6 +304,8 @@ QgsPalLayerSettings::QgsPalLayerSettings()
obstacleFactor = 1.0;
obstacleType = PolygonInterior;
zIndex = 0.0;
mCallout = qgis::make_unique< QgsSimpleLineCallout >();
}
Q_NOWARN_DEPRECATED_POP
@ -395,6 +398,8 @@ QgsPalLayerSettings &QgsPalLayerSettings::operator=( const QgsPalLayerSettings &
mFormat = s.mFormat;
mDataDefinedProperties = s.mDataDefinedProperties;
mCallout.reset( s.mCallout ? s.mCallout->clone() : nullptr );
geometryGenerator = s.geometryGenerator;
geometryGeneratorEnabled = s.geometryGeneratorEnabled;
geometryGeneratorType = s.geometryGeneratorType;
@ -512,10 +517,19 @@ bool QgsPalLayerSettings::prepare( QgsRenderContext &context, QSet<QString> &att
}
}
if ( mCallout )
{
const auto referencedColumns = mCallout->referencedFields( context );
for ( const QString &name : referencedColumns )
{
attributeNames.insert( name );
}
}
return true;
}
void QgsPalLayerSettings::startRender( QgsRenderContext & )
void QgsPalLayerSettings::startRender( QgsRenderContext &context )
{
if ( mRenderStarted )
{
@ -523,10 +537,15 @@ void QgsPalLayerSettings::startRender( QgsRenderContext & )
return;
}
if ( mCallout )
{
mCallout->startRender( context );
}
mRenderStarted = true;
}
void QgsPalLayerSettings::stopRender( QgsRenderContext & )
void QgsPalLayerSettings::stopRender( QgsRenderContext &context )
{
if ( !mRenderStarted )
{
@ -534,6 +553,11 @@ void QgsPalLayerSettings::stopRender( QgsRenderContext & )
return;
}
if ( mCallout )
{
mCallout->stopRender( context );
}
mRenderStarted = false;
}
@ -1108,7 +1132,11 @@ void QgsPalLayerSettings::readXml( const QDomElement &elem, const QgsReadWriteCo
mDataDefinedProperties.setProperty( MaxScale, QgsProperty() );
}
// TODO - replace with registry when multiple callout styles exist
if ( !mCallout )
mCallout = qgis::make_unique< QgsSimpleLineCallout >();
mCallout->restoreProperties( elem.firstChildElement( QStringLiteral( "callout" ) ), context );
}
@ -1204,9 +1232,20 @@ QDomElement QgsPalLayerSettings::writeXml( QDomDocument &doc, const QgsReadWrite
elem.appendChild( placementElem );
elem.appendChild( renderingElem );
elem.appendChild( ddElem );
if ( mCallout )
{
mCallout->saveProperties( doc, elem );
}
return elem;
}
void QgsPalLayerSettings::setCallout( QgsCallout *callout )
{
mCallout.reset( callout );
}
QPixmap QgsPalLayerSettings::labelSettingsPreviewPixmap( const QgsPalLayerSettings &settings, QSize size, const QString &previewText, int padding )
{
// for now, just use format

View File

@ -70,7 +70,7 @@ class QgsVectorLayerLabelProvider;
class QgsDxfExport;
class QgsVectorLayerDiagramProvider;
class QgsExpressionContext;
class QgsCallout;
/**
* \ingroup core
@ -951,6 +951,26 @@ class CORE_EXPORT QgsPalLayerSettings
*/
void setFormat( const QgsTextFormat &format ) { mFormat = format; }
/**
* Returns the label callout renderer, responsible for drawing label callouts.
*
* Ownership is not transferred.
*
* \see setCallout()
* \since QGIS 3.10
*/
QgsCallout *callout() const { return mCallout.get(); }
/**
* Sets the label \a callout renderer, responsible for drawing label callouts.
*
* Ownership of \a callout is transferred to the settings.
* \see callout()
* \since QGIS 3.10
*/
void setCallout( QgsCallout *callout SIP_TRANSFER );
/**
* Returns a pixmap preview for label \a settings.
* \param settings label settings
@ -1050,6 +1070,8 @@ class CORE_EXPORT QgsPalLayerSettings
QgsTextFormat mFormat;
std::unique_ptr< QgsCallout > mCallout;
QgsExpression mGeometryGeneratorExpression;
bool mRenderStarted = false;

View File

@ -30,6 +30,7 @@
#include "feature.h"
#include "labelposition.h"
#include "callouts/qgscallout.h"
#include <QPicture>
@ -347,6 +348,20 @@ void QgsVectorLayerLabelProvider::drawLabel( QgsRenderContext &context, pal::Lab
// Render the components of a label in reverse order
// (backgrounds -> text)
// render callout
if ( mSettings.callout() )
{
QgsMapToPixel xform = context.mapToPixel();
xform.setMapRotation( 0, 0, 0 );
QPointF outPt = xform.transform( label->getX(), label->getY() ).toQPointF();
QgsPointXY outPt2 = xform.transform( label->getX() + label->getWidth(), label->getY() + label->getHeight() );
QRectF rect( outPt.x(), outPt.y(), outPt2.x() - outPt.x(), outPt2.y() - outPt.y() );
QgsGeometry g( QgsGeos::fromGeos( label->getFeaturePart()->feature()->geometry() ) );
g.transform( xform.transform() );
mSettings.callout()->render( context, rect, label->getAlpha() * 180 / M_PI, g );
}
if ( tmpLyr.format().shadow().enabled() && tmpLyr.format().shadow().shadowPlacement() == QgsTextShadowSettings::ShadowLowest )
{
QgsTextFormat format = tmpLyr.format();

View File

@ -999,6 +999,7 @@ INCLUDE_DIRECTORIES(
${CMAKE_SOURCE_DIR}/src/core
${CMAKE_SOURCE_DIR}/src/core/annotations
${CMAKE_SOURCE_DIR}/src/core/auth
${CMAKE_SOURCE_DIR}/src/core/callouts
${CMAKE_SOURCE_DIR}/src/core/fieldformatter
${CMAKE_SOURCE_DIR}/src/core/geometry
${CMAKE_SOURCE_DIR}/src/core/layertree

View File

@ -25,6 +25,7 @@
#include "qgsexpressioncontextutils.h"
#include "qgsexpressionbuilderdialog.h"
#include "qgsstylesavedialog.h"
#include "qgscallout.h"
#include <QButtonGroup>
#include <QMessageBox>
@ -274,6 +275,12 @@ void QgsLabelingGui::setLayer( QgsMapLayer *mapLayer )
mDataDefinedProperties = mSettings.dataDefinedProperties();
// callout settings, to move to custom widget when multiple styles exist
mCalloutLineStyleButton->setLayer( mLayer );
if ( mSettings.callout() )
mCalloutLineStyleButton->setSymbol( static_cast< QgsSimpleLineCallout * >( mSettings.callout() )->lineSymbol()->clone() );
mCalloutsDrawCheckBox->setChecked( mSettings.callout()->enabled() );
updatePlacementWidgets();
updateLinePlacementOptions();
@ -454,6 +461,13 @@ QgsPalLayerSettings QgsLabelingGui::layerSettings()
lyr.setDataDefinedProperties( mDataDefinedProperties );
// callout settings, to move to custom widget when multiple styles exist
// callout settings, to move to custom widget when multiple styles exist
std::unique_ptr< QgsSimpleLineCallout > callout = qgis::make_unique< QgsSimpleLineCallout >();
callout->setEnabled( mCalloutsDrawCheckBox->isChecked() );
callout->setLineSymbol( mCalloutLineStyleButton->clonedSymbol< QgsLineSymbol >() );
lyr.setCallout( callout.release() );
return lyr;
}

View File

@ -322,6 +322,12 @@ void QgsTextFormatWidget::initWidget()
setDockMode( false );
// Callout options - to move to custom widgets when multiple callout styles exist
mCalloutLineStyleButton->setSymbolType( QgsSymbol::Line );
mCalloutLineStyleButton->setDialogTitle( tr( "Callout Symbol" ) );
mCalloutLineStyleButton->registerExpressionContextGenerator( this );
mCalloutLineStyleButton->setMapCanvas( mMapCanvas );
QList<QWidget *> widgets;
widgets << btnBufferColor
<< btnTextColor
@ -539,7 +545,9 @@ void QgsTextFormatWidget::initWidget()
<< mGeometryGenerator
<< mGeometryGeneratorType
<< mLinePlacementFlagsDDBtn
<< mBackgroundSymbolButton;
<< mBackgroundSymbolButton
<< mCalloutLineStyleButton
<< mCalloutsDrawCheckBox;
connectValueChanged( widgets, SLOT( updatePreview() ) );
connect( mQuadrantBtnGrp, static_cast<void ( QButtonGroup::* )( int )>( &QButtonGroup::buttonClicked ), this, &QgsTextFormatWidget::updatePreview );

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>828</width>
<height>1081</height>
<height>608</height>
</rect>
</property>
<property name="windowTitle">
@ -112,7 +112,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>807</width>
<width>806</width>
<height>300</height>
</rect>
</property>
@ -335,7 +335,7 @@
<enum>QTabWidget::Rounded</enum>
</property>
<property name="currentIndex">
<number>0</number>
<number>5</number>
</property>
<property name="iconSize">
<size>
@ -397,6 +397,18 @@
<string/>
</attribute>
</widget>
<widget class="QWidget" name="callouts">
<attribute name="icon">
<iconset resource="../../images/images.qrc">
<normaloff>:/images/themes/default/mIconSnappingSegment.svg</normaloff>:/images/themes/default/mIconSnappingSegment.svg</iconset>
</attribute>
<attribute name="title">
<string/>
</attribute>
<attribute name="toolTip">
<string>Callouts</string>
</attribute>
</widget>
<widget class="QWidget" name="widget">
<attribute name="icon">
<iconset resource="../../images/images.qrc">
@ -534,6 +546,18 @@
<normaloff>:/images/themes/default/propertyicons/labelshadow.svg</normaloff>:/images/themes/default/propertyicons/labelshadow.svg</iconset>
</property>
</item>
<item>
<property name="text">
<string>Callouts</string>
</property>
<property name="toolTip">
<string>Callouts</string>
</property>
<property name="icon">
<iconset resource="../../images/images.qrc">
<normaloff>:/images/themes/default/mIconSnappingSegment.svg</normaloff>:/images/themes/default/mIconSnappingSegment.svg</iconset>
</property>
</item>
<item>
<property name="text">
<string>Placement</string>
@ -577,21 +601,21 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="leftMargin">
<number>6</number>
<number>0</number>
</property>
<property name="topMargin">
<number>6</number>
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>6</number>
<number>0</number>
</property>
<item>
<widget class="QStackedWidget" name="mLabelStackedWidget">
<property name="currentIndex">
<number>4</number>
<number>5</number>
</property>
<widget class="QWidget" name="mLabelPage_Text">
<layout class="QVBoxLayout" name="verticalLayout_6">
@ -620,8 +644,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>770</width>
<height>866</height>
<width>311</width>
<height>292</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,1">
@ -1203,8 +1227,8 @@ font-style: italic;</string>
<rect>
<x>0</x>
<y>0</y>
<width>770</width>
<height>847</height>
<width>374</width>
<height>644</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_42">
@ -2078,8 +2102,8 @@ font-style: italic;</string>
<rect>
<x>0</x>
<y>0</y>
<width>770</width>
<height>866</height>
<width>308</width>
<height>308</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_12">
@ -2424,8 +2448,8 @@ font-style: italic;</string>
<rect>
<x>0</x>
<y>0</y>
<width>770</width>
<height>866</height>
<width>474</width>
<height>786</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_21">
@ -3185,8 +3209,8 @@ font-style: italic;</string>
<rect>
<x>0</x>
<y>0</y>
<width>770</width>
<height>866</height>
<width>768</width>
<height>457</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_22">
@ -3586,6 +3610,151 @@ font-style: italic;</string>
</item>
</layout>
</widget>
<widget class="QWidget" name="page">
<layout class="QVBoxLayout" name="verticalLayout_14">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QScrollArea" name="scrollArea_6">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents_7">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>782</width>
<height>387</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_46">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0">
<layout class="QGridLayout" name="gridLayout_43">
<item row="0" column="0">
<widget class="QLabel" name="label_51">
<property name="text">
<string>Callouts</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QFrame" name="mShadowFrame_2">
<layout class="QGridLayout" name="gridLayout_44">
<property name="leftMargin">
<number>20</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item row="1" column="1">
<spacer name="horizontalSpacer_15">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_45">
<property name="topMargin">
<number>20</number>
</property>
<item row="0" column="1">
<widget class="QgsSymbolButton" name="mCalloutLineStyleButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Symbol…</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Line style</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="mCalloutsDrawCheckBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Draw callouts</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="mLabelPage_Placement">
<layout class="QVBoxLayout" name="verticalLayout_10">
<property name="leftMargin">
@ -3626,8 +3795,8 @@ font-style: italic;</string>
<rect>
<x>0</x>
<y>0</y>
<width>335</width>
<height>884</height>
<width>768</width>
<height>1095</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_11">
@ -5334,8 +5503,8 @@ font-style: italic;</string>
<rect>
<x>0</x>
<y>0</y>
<width>302</width>
<height>674</height>
<width>415</width>
<height>852</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_8">
@ -6622,7 +6791,6 @@ font-style: italic;</string>
</tabstops>
<resources>
<include location="../../images/images.qrc"/>
<include location="../../images/images.qrc"/>
</resources>
<connections>
<connection>