Use a registry for callouts

This commit is contained in:
Nyall Dawson 2019-07-05 11:50:07 +10:00
parent b298a197e7
commit 8575f95c89
11 changed files with 347 additions and 44 deletions

View File

@ -195,7 +195,8 @@ A simple direct line callout style.
public:
QgsSimpleLineCallout();
QgsSimpleLineCallout( const QgsSimpleLineCallout &other );
~QgsSimpleLineCallout();
static QgsCallout *create( const QVariantMap &properties = QVariantMap(), const QgsReadWriteContext &context = QgsReadWriteContext() ) /Factory/;
%Docstring
@ -227,6 +228,9 @@ QgsSimpleLineCallout.properties() ).
virtual void draw( QgsRenderContext &context, QRectF rect, const double angle, const QgsGeometry &anchor );
private:
QgsSimpleLineCallout( const QgsSimpleLineCallout &other );
QgsSimpleLineCallout &operator=( const QgsSimpleLineCallout & );
};
@ -239,7 +243,7 @@ class QgsManhattanLineCallout : QgsSimpleLineCallout
public:
QgsManhattanLineCallout();
QgsManhattanLineCallout( const QgsManhattanLineCallout &other );
static QgsCallout *create( const QVariantMap &properties = QVariantMap(), const QgsReadWriteContext &context = QgsReadWriteContext() ) /Factory/;
%Docstring
@ -257,6 +261,9 @@ QgsManhattanLineCallout.properties() ).
virtual void draw( QgsRenderContext &context, QRectF rect, const double angle, const QgsGeometry &anchor );
private:
QgsManhattanLineCallout( const QgsManhattanLineCallout &other );
QgsManhattanLineCallout &operator=( const QgsManhattanLineCallout & );
};

View File

@ -120,7 +120,7 @@ Returns the metadata for specified the specified callout ``type``. Returns ``Non
%Docstring
Registers a new callout type.
Owership of ``metadata`` is transferred to the registry.
Ownership of ``metadata`` is transferred to the registry.
%End
QgsCallout *createCallout( const QString &type, const QVariantMap &properties = QVariantMap(), const QgsReadWriteContext &context = QgsReadWriteContext() ) const /Factory/;
@ -135,6 +135,11 @@ The caller takes ownership of the callout.
Creates a new instance of a callout of the specified ``type``, using the properties from a DOM ``element``.
The caller takes ownership of the callout.
%End
QStringList calloutTypes() const;
%Docstring
Returns a list of all available callout types.
%End
static QgsCallout *defaultCallout() /Factory/;

View File

@ -116,6 +116,8 @@ QgsSimpleLineCallout::QgsSimpleLineCallout()
}
QgsSimpleLineCallout::~QgsSimpleLineCallout() = default;
QgsSimpleLineCallout::QgsSimpleLineCallout( const QgsSimpleLineCallout &other )
: QgsCallout( other )
, mLineSymbol( other.mLineSymbol ? other.mLineSymbol->clone() : nullptr )
@ -123,11 +125,6 @@ QgsSimpleLineCallout::QgsSimpleLineCallout( const QgsSimpleLineCallout &other )
}
QgsSimpleLineCallout &QgsSimpleLineCallout::operator=( const QgsSimpleLineCallout &other )
{
mLineSymbol.reset( other.mLineSymbol ? other.mLineSymbol->clone() : nullptr );
}
QgsCallout *QgsSimpleLineCallout::create( const QVariantMap &properties, const QgsReadWriteContext &context )
{
std::unique_ptr< QgsSimpleLineCallout > callout = qgis::make_unique< QgsSimpleLineCallout >();
@ -250,9 +247,6 @@ QgsManhattanLineCallout::QgsManhattanLineCallout( const QgsManhattanLineCallout
}
QgsManhattanLineCallout &QgsManhattanLineCallout::operator=( const QgsManhattanLineCallout &other )
{
}
QgsCallout *QgsManhattanLineCallout::create( const QVariantMap &properties, const QgsReadWriteContext &context )
{

View File

@ -29,6 +29,7 @@ class QgsLineSymbol;
class QgsGeometry;
class QgsRenderContext;
class QgsCalloutWidget; //stop sip breaking
/**
* \ingroup core
@ -201,8 +202,12 @@ class CORE_EXPORT QgsSimpleLineCallout : public QgsCallout
public:
QgsSimpleLineCallout();
~QgsSimpleLineCallout() override;
#ifndef SIP_RUN
QgsSimpleLineCallout( const QgsSimpleLineCallout &other );
QgsSimpleLineCallout &operator=( const QgsSimpleLineCallout & );
QgsSimpleLineCallout &operator=( const QgsSimpleLineCallout & ) = delete;
#endif
/**
* Creates a new QgsSimpleLineCallout, using the settings
@ -228,6 +233,11 @@ class CORE_EXPORT QgsSimpleLineCallout : public QgsCallout
private:
#ifdef SIP_RUN
QgsSimpleLineCallout( const QgsSimpleLineCallout &other );
QgsSimpleLineCallout &operator=( const QgsSimpleLineCallout & );
#endif
std::unique_ptr< QgsLineSymbol > mLineSymbol;
};
@ -237,8 +247,11 @@ class CORE_EXPORT QgsManhattanLineCallout : public QgsSimpleLineCallout
public:
QgsManhattanLineCallout();
#ifndef SIP_RUN
QgsManhattanLineCallout( const QgsManhattanLineCallout &other );
QgsManhattanLineCallout &operator=( const QgsManhattanLineCallout & );
QgsManhattanLineCallout &operator=( const QgsManhattanLineCallout & ) = delete;
#endif
/**
* Creates a new QgsManhattanLineCallout, using the settings
@ -253,6 +266,11 @@ class CORE_EXPORT QgsManhattanLineCallout : public QgsSimpleLineCallout
protected:
void draw( QgsRenderContext &context, QRectF rect, const double angle, const QgsGeometry &anchor ) override;
private:
#ifdef SIP_RUN
QgsManhattanLineCallout( const QgsManhattanLineCallout &other );
QgsManhattanLineCallout &operator=( const QgsManhattanLineCallout & );
#endif
};

View File

@ -48,8 +48,8 @@ QgsCalloutWidget* QgsCalloutMetadata::createCalloutWidget(QgsVectorLayer* vl)
QgsCalloutRegistry::QgsCalloutRegistry()
{
// init registry with known callouts
addCalloutType( new QgsCalloutMetadata( QStringLiteral( "SimpleLine" ), QObject::tr( "Simple lines" ), QgsSimpleLineCallout::create ) );
addCalloutType( new QgsCalloutMetadata( QStringLiteral( "ManhattanLine" ), QObject::tr( "Manhattan lines" ), QgsManhattanLineCallout::create ) );
addCalloutType( new QgsCalloutMetadata( QStringLiteral( "simple" ), QObject::tr( "Simple lines" ), QgsSimpleLineCallout::create ) );
addCalloutType( new QgsCalloutMetadata( QStringLiteral( "manhattan" ), QObject::tr( "Manhattan lines" ), QgsManhattanLineCallout::create ) );
}
QgsCalloutRegistry::~QgsCalloutRegistry()
@ -72,6 +72,11 @@ QgsCallout* QgsCalloutRegistry::createCallout(const QString& name, const QDomEle
return createCallout( name, props, context );
}
QStringList QgsCalloutRegistry::calloutTypes() const
{
return mMetadata.keys();
}
QgsCalloutAbstractMetadata *QgsCalloutRegistry::calloutMetadata( const QString &name ) const
{
return mMetadata.value( name );

View File

@ -148,7 +148,7 @@ class CORE_EXPORT QgsCalloutRegistry
/**
* Registers a new callout type.
*
* Owership of \a metadata is transferred to the registry.
* Ownership of \a metadata is transferred to the registry.
*/
bool addCalloutType( QgsCalloutAbstractMetadata *metadata SIP_TRANSFER );
@ -166,6 +166,11 @@ class CORE_EXPORT QgsCalloutRegistry
*/
QgsCallout *createCallout( const QString &type, const QDomElement &element, const QgsReadWriteContext &context ) const SIP_FACTORY;
/**
* Returns a list of all available callout types.
*/
QStringList calloutTypes() const;
/**
* Create a new instance of a callout with default settings.
*

View File

@ -0,0 +1,131 @@
/***************************************************************************
qgscalloutwidget.cpp
---------------------
begin : July 2019
copyright : (C) 2019 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgscalloutwidget.h"
#include "qgsvectorlayer.h"
#include "qgsexpressioncontextutils.h"
#include "qgsunitselectionwidget.h"
#include "qgscallout.h"
#include "qgsnewauxiliaryfielddialog.h"
#include "qgsnewauxiliarylayerdialog.h"
#include "qgsauxiliarystorage.h"
QgsExpressionContext QgsCalloutWidget::createExpressionContext() const
{
if ( mContext.expressionContext() )
return *mContext.expressionContext();
QgsExpressionContext expContext( mContext.globalProjectAtlasMapLayerScopes( vectorLayer() ) );
QgsExpressionContextScope *symbolScope = QgsExpressionContextUtils::updateSymbolScope( nullptr, new QgsExpressionContextScope() );
if ( const QgsCallout *callout = const_cast< QgsCalloutWidget * >( this )->callout() )
{
//cheat a bit - set the symbol color variable to match the symbol layer's color (when we should really be using the *symbols*
//color, but that's not accessible here). 99% of the time these will be the same anyway
// symbolScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_SYMBOL_COLOR, symbolLayer->color(), true ) );
}
expContext << symbolScope;
// additional scopes
const auto constAdditionalExpressionContextScopes = mContext.additionalExpressionContextScopes();
for ( const QgsExpressionContextScope &scope : constAdditionalExpressionContextScopes )
{
expContext.appendScope( new QgsExpressionContextScope( scope ) );
}
//TODO - show actual value
expContext.setOriginalValueVariable( QVariant() );
expContext.setHighlightedVariables( QStringList() << QgsExpressionContext::EXPR_ORIGINAL_VALUE << QgsExpressionContext::EXPR_SYMBOL_COLOR );
return expContext;
}
void QgsCalloutWidget::setContext( const QgsSymbolWidgetContext &context )
{
mContext = context;
const auto unitSelectionWidgets = findChildren<QgsUnitSelectionWidget *>();
for ( QgsUnitSelectionWidget *unitWidget : unitSelectionWidgets )
{
unitWidget->setMapCanvas( mContext.mapCanvas() );
}
}
QgsSymbolWidgetContext QgsCalloutWidget::context() const
{
return mContext;
}
void QgsCalloutWidget::registerDataDefinedButton( QgsPropertyOverrideButton *button, QgsSymbolLayer::Property key )
{
// button->init( key, callout()->dataDefinedProperties(), QgsSymbolLayer::propertyDefinitions(), mVectorLayer, true );
connect( button, &QgsPropertyOverrideButton::changed, this, &QgsCalloutWidget::updateDataDefinedProperty );
connect( button, &QgsPropertyOverrideButton::createAuxiliaryField, this, &QgsCalloutWidget::createAuxiliaryField );
button->registerExpressionContextGenerator( this );
}
void QgsCalloutWidget::createAuxiliaryField()
{
// try to create an auxiliary layer if not yet created
if ( !mVectorLayer->auxiliaryLayer() )
{
QgsNewAuxiliaryLayerDialog dlg( mVectorLayer, this );
dlg.exec();
}
// return if still not exists
if ( !mVectorLayer->auxiliaryLayer() )
return;
QgsPropertyOverrideButton *button = qobject_cast<QgsPropertyOverrideButton *>( sender() );
QgsSymbolLayer::Property key = static_cast< QgsSymbolLayer::Property >( button->propertyKey() );
QgsPropertyDefinition def = QgsSymbolLayer::propertyDefinitions()[key];
// create property in auxiliary storage if necessary
if ( !mVectorLayer->auxiliaryLayer()->exists( def ) )
{
QgsNewAuxiliaryFieldDialog dlg( def, mVectorLayer, true, this );
if ( dlg.exec() == QDialog::Accepted )
def = dlg.propertyDefinition();
}
// return if still not exist
if ( !mVectorLayer->auxiliaryLayer()->exists( def ) )
return;
// update property with join field name from auxiliary storage
QgsProperty property = button->toProperty();
property.setField( QgsAuxiliaryLayer::nameFromProperty( def, true ) );
property.setActive( true );
button->updateFieldLists();
button->setToProperty( property );
#if 0
callout()->setDataDefinedProperty( key, button->toProperty() );
#endif
emit changed();
}
void QgsCalloutWidget::updateDataDefinedProperty()
{
QgsPropertyOverrideButton *button = qobject_cast<QgsPropertyOverrideButton *>( sender() );
QgsSymbolLayer::Property key = static_cast< QgsSymbolLayer::Property >( button->propertyKey() );
#if 0
callout()->setDataDefinedProperty( key, button->toProperty() );
#endif
emit changed();
}

View File

@ -0,0 +1,108 @@
/***************************************************************************
qgscalloutwidget.h
---------------------
begin : July 2019
copyright : (C) 2019 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSCALLOUTWIDGET_H
#define QGSCALLOUTWIDGET_H
#include "qgspropertyoverridebutton.h"
#include "qgis_sip.h"
#include "qgssymbolwidgetcontext.h"
#include "qgssymbollayer.h"
#include <QWidget>
#include <QStandardItemModel>
class QgsVectorLayer;
class QgsMapCanvas;
class QgsCallout;
/**
* \ingroup gui
* \class QgsCalloutWidget
*/
class GUI_EXPORT QgsCalloutWidget : public QWidget, protected QgsExpressionContextGenerator
{
Q_OBJECT
public:
/**
* Constructor for QgsCalloutWidget.
* \param vl associated vector layer
* \param parent parent widget
*/
QgsCalloutWidget( QWidget *parent SIP_TRANSFERTHIS, QgsVectorLayer *vl = nullptr )
: QWidget( parent )
, mVectorLayer( vl )
{}
virtual void setCallout( QgsCallout *callout ) = 0;
virtual QgsCallout *callout() = 0;
/**
* Sets the context in which the symbol widget is shown, e.g., the associated map canvas and expression contexts.
* \param context symbol widget context
* \see context()
* \since QGIS 3.0
*/
virtual void setContext( const QgsSymbolWidgetContext &context );
/**
* Returns the context in which the symbol widget is shown, e.g., the associated map canvas and expression contexts.
* \see setContext()
* \since QGIS 3.0
*/
QgsSymbolWidgetContext context() const;
/**
* Returns the vector layer associated with the widget.
* \since QGIS 2.12
*/
const QgsVectorLayer *vectorLayer() const { return mVectorLayer; }
protected:
/**
* Registers a data defined override button. Handles setting up connections
* for the button and initializing the button to show the correct descriptions
* and help text for the associated property.
* \since QGIS 3.0
*/
void registerDataDefinedButton( QgsPropertyOverrideButton *button, QgsSymbolLayer::Property key );
QgsExpressionContext createExpressionContext() const override;
private:
QgsVectorLayer *mVectorLayer = nullptr;
QgsMapCanvas *mMapCanvas = nullptr;
signals:
/**
* Should be emitted whenever configuration changes happened on this symbol layer configuration.
* If the subsymbol is changed, symbolChanged() should be emitted instead.
*/
void changed();
protected slots:
void updateDataDefinedProperty();
private slots:
void createAuxiliaryField();
private:
QgsSymbolWidgetContext mContext;
};
#endif // QGSCALLOUTWIDGET_H

View File

@ -26,6 +26,8 @@
#include "qgsexpressionbuilderdialog.h"
#include "qgsstylesavedialog.h"
#include "qgscallout.h"
#include "qgsapplication.h"
#include "qgscalloutsregistry.h"
#include <QButtonGroup>
#include <QMessageBox>
@ -100,6 +102,12 @@ QgsLabelingGui::QgsLabelingGui( QgsVectorLayer *layer, QgsMapCanvas *mapCanvas,
mMaxScaleWidget->setMapCanvas( mCanvas );
mMaxScaleWidget->setShowCurrentScaleButton( true );
const QStringList calloutTypes = QgsApplication::calloutRegistry()->calloutTypes();
for ( const QString &type : calloutTypes )
{
mCalloutStyleComboBox->addItem( QgsApplication::calloutRegistry()->calloutMetadata( type )->visibleName(), type );
}
mGeometryGeneratorWarningLabel->setStyleSheet( QStringLiteral( "color: #FFC107;" ) );
mGeometryGeneratorWarningLabel->setTextInteractionFlags( Qt::TextBrowserInteraction );
connect( mGeometryGeneratorWarningLabel, &QLabel::linkActivated, this, [this]( const QString & link )
@ -278,8 +286,17 @@ void QgsLabelingGui::setLayer( QgsMapLayer *mapLayer )
// 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() );
whileBlocking( mCalloutsDrawCheckBox )->setChecked( mSettings.callout()->enabled() );
whileBlocking( mCalloutStyleComboBox )->setCurrentIndex( mCalloutStyleComboBox->findData( mSettings.callout()->type() ) );
}
else
{
std::unique_ptr< QgsCallout > defaultCallout( QgsApplication::calloutRegistry()->defaultCallout() );
whileBlocking( mCalloutStyleComboBox )->setCurrentIndex( mCalloutStyleComboBox->findData( defaultCallout->type() ) );
whileBlocking( mCalloutsDrawCheckBox )->setChecked( false );
}
updatePlacementWidgets();
updateLinePlacementOptions();
@ -461,10 +478,12 @@ QgsPalLayerSettings QgsLabelingGui::layerSettings()
lyr.setDataDefinedProperties( mDataDefinedProperties );
// callout settings, to move to custom widget when multiple styles exist
std::unique_ptr< QgsManhattanLineCallout > callout = qgis::make_unique< QgsManhattanLineCallout >();
// callout settings
const QString calloutType = mCalloutStyleComboBox->currentData().toString();
std::unique_ptr< QgsCallout > callout( QgsApplication::calloutRegistry()->createCallout( calloutType ) );
callout->setEnabled( mCalloutsDrawCheckBox->isChecked() );
callout->setLineSymbol( mCalloutLineStyleButton->clonedSymbol< QgsLineSymbol >() );
// todo - move to custom widget
static_cast< QgsSimpleLineCallout * >( callout.get() )->setLineSymbol( mCalloutLineStyleButton->clonedSymbol< QgsLineSymbol >() );
lyr.setCallout( callout.release() );
return lyr;

View File

@ -547,7 +547,8 @@ void QgsTextFormatWidget::initWidget()
<< mLinePlacementFlagsDDBtn
<< mBackgroundSymbolButton
<< mCalloutLineStyleButton
<< mCalloutsDrawCheckBox;
<< mCalloutsDrawCheckBox
<< mCalloutStyleComboBox;
connectValueChanged( widgets, SLOT( updatePreview() ) );
connect( mQuadrantBtnGrp, static_cast<void ( QButtonGroup::* )( int )>( &QButtonGroup::buttonClicked ), this, &QgsTextFormatWidget::updatePreview );

View File

@ -3209,8 +3209,8 @@ font-style: italic;</string>
<rect>
<x>0</x>
<y>0</y>
<width>776</width>
<height>435</height>
<width>259</width>
<height>350</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_22">
@ -3715,7 +3715,7 @@ font-style: italic;</string>
<property name="topMargin">
<number>0</number>
</property>
<item row="0" column="1">
<item row="1" column="1">
<widget class="QgsSymbolButton" name="mCalloutLineStyleButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
@ -3728,13 +3728,23 @@ font-style: italic;</string>
</property>
</widget>
</item>
<item row="0" column="0">
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Line style</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="mCalloutStyleComboBox"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Style</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">