QGIS/src/core/vector/qgsvectorlayer.cpp

6571 lines
210 KiB
C++

/***************************************************************************
qgsvectorlayer.cpp
--------------------
begin : Oct 29, 2003
copyright : (C) 2003 by Gary E.Sherman
email : sherman at mrcc.com
This class implements a generic means to display vector layers. The features
and attributes are read from the data store using a "data provider" plugin.
QgsVectorLayer can be used with any data store for which an appropriate
plugin is available.
***************************************************************************/
/***************************************************************************
* *
* 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 "qgis.h" //for globals
#include "qgssettings.h"
#include "qgsvectorlayer.h"
#include "moc_qgsvectorlayer.cpp"
#include "qgsactionmanager.h"
#include "qgsapplication.h"
#include "qgsconditionalstyle.h"
#include "qgscoordinatereferencesystem.h"
#include "qgscurve.h"
#include "qgsdatasourceuri.h"
#include "qgsexpressionfieldbuffer.h"
#include "qgsexpressionnodeimpl.h"
#include "qgsfeature.h"
#include "qgsfeaturerequest.h"
#include "qgsfields.h"
#include "qgsmaplayerfactory.h"
#include "qgsmaplayerstylemanager.h"
#include "qgsgeometry.h"
#include "qgslayermetadataformatter.h"
#include "qgslogger.h"
#include "qgsmaplayerlegend.h"
#include "qgsmessagelog.h"
#include "qgsogcutils.h"
#include "qgspainting.h"
#include "qgspointxy.h"
#include "qgsproject.h"
#include "qgsproviderregistry.h"
#include "qgsrectangle.h"
#include "qgsrelationmanager.h"
#include "qgsweakrelation.h"
#include "qgsrendercontext.h"
#include "qgsvectordataprovider.h"
#include "qgsvectorlayertemporalproperties.h"
#include "qgsvectorlayerelevationproperties.h"
#include "qgsvectorlayereditbuffer.h"
#include "qgsvectorlayereditpassthrough.h"
#include "qgsvectorlayereditutils.h"
#include "qgsvectorlayerfeatureiterator.h"
#include "qgsvectorlayerjoinbuffer.h"
#include "qgsvectorlayerlabeling.h"
#include "qgsvectorlayerrenderer.h"
#include "qgsvectorlayerfeaturecounter.h"
#include "qgsvectorlayerselectionproperties.h"
#include "qgspoint.h"
#include "qgsrenderer.h"
#include "qgssymbollayer.h"
#include "qgsdiagramrenderer.h"
#include "qgspallabeling.h"
#include "qgsrulebasedlabeling.h"
#include "qgsstoredexpressionmanager.h"
#include "qgsexpressioncontext.h"
#include "qgsfeedback.h"
#include "qgsxmlutils.h"
#include "qgstaskmanager.h"
#include "qgstransaction.h"
#include "qgsauxiliarystorage.h"
#include "qgsgeometryoptions.h"
#include "qgsexpressioncontextutils.h"
#include "qgsruntimeprofiler.h"
#include "qgsfeaturerenderergenerator.h"
#include "qgsvectorlayerutils.h"
#include "qgsvectorlayerprofilegenerator.h"
#include "qgsprofilerequest.h"
#include "qgssymbollayerutils.h"
#include "qgsthreadingutils.h"
#include "qgssldexportcontext.h"
#include <QDir>
#include <QFile>
#include <QImage>
#include <QPainter>
#include <QPainterPath>
#include <QPolygonF>
#include <QProgressDialog>
#include <QString>
#include <QDomNode>
#include <QVector>
#include <QStringBuilder>
#include <QUrl>
#include <QUndoCommand>
#include <QUrlQuery>
#include <QUuid>
#include <QRegularExpression>
#include <QTimer>
#include <limits>
#include <optional>
#include "qgssettingsentryenumflag.h"
#include "qgssettingsentryimpl.h"
#include "qgssettingstree.h"
const QgsSettingsEntryDouble *QgsVectorLayer::settingsSimplifyDrawingTol = new QgsSettingsEntryDouble( QStringLiteral( "simplifyDrawingTol" ), QgsSettingsTree::sTreeQgis, Qgis::DEFAULT_MAPTOPIXEL_THRESHOLD );
const QgsSettingsEntryBool *QgsVectorLayer::settingsSimplifyLocal = new QgsSettingsEntryBool( QStringLiteral( "simplifyLocal" ), QgsSettingsTree::sTreeQgis, true );
const QgsSettingsEntryDouble *QgsVectorLayer::settingsSimplifyMaxScale = new QgsSettingsEntryDouble( QStringLiteral( "simplifyMaxScale" ), QgsSettingsTree::sTreeQgis, 1.0 );
const QgsSettingsEntryEnumFlag<Qgis::VectorRenderingSimplificationFlags> *QgsVectorLayer::settingsSimplifyDrawingHints = new QgsSettingsEntryEnumFlag<Qgis::VectorRenderingSimplificationFlags>( QStringLiteral( "simplifyDrawingHints" ), QgsSettingsTree::sTreeQgis, Qgis::VectorRenderingSimplificationFlag::NoSimplification );
const QgsSettingsEntryEnumFlag<Qgis::VectorSimplificationAlgorithm> *QgsVectorLayer::settingsSimplifyAlgorithm = new QgsSettingsEntryEnumFlag<Qgis::VectorSimplificationAlgorithm>( QStringLiteral( "simplifyAlgorithm" ), QgsSettingsTree::sTreeQgis, Qgis::VectorSimplificationAlgorithm::Distance );
#ifdef TESTPROVIDERLIB
#include <dlfcn.h>
#endif
typedef bool saveStyle_t(
const QString &uri,
const QString &qmlStyle,
const QString &sldStyle,
const QString &styleName,
const QString &styleDescription,
const QString &uiFileContent,
bool useAsDefault,
QString &errCause
);
typedef QString loadStyle_t(
const QString &uri,
QString &errCause
);
typedef int listStyles_t(
const QString &uri,
QStringList &ids,
QStringList &names,
QStringList &descriptions,
QString &errCause
);
typedef QString getStyleById_t(
const QString &uri,
QString styleID,
QString &errCause
);
typedef bool deleteStyleById_t(
const QString &uri,
QString styleID,
QString &errCause
);
QgsVectorLayer::QgsVectorLayer( const QString &vectorLayerPath,
const QString &baseName,
const QString &providerKey,
const QgsVectorLayer::LayerOptions &options )
: QgsMapLayer( Qgis::LayerType::Vector, baseName, vectorLayerPath )
, mSelectionProperties( new QgsVectorLayerSelectionProperties( this ) )
, mTemporalProperties( new QgsVectorLayerTemporalProperties( this ) )
, mElevationProperties( new QgsVectorLayerElevationProperties( this ) )
, mAuxiliaryLayer( nullptr )
, mAuxiliaryLayerKey( QString() )
, mReadExtentFromXml( options.readExtentFromXml )
, mRefreshRendererTimer( new QTimer( this ) )
{
mShouldValidateCrs = !options.skipCrsValidation;
mLoadAllStoredStyle = options.loadAllStoredStyles;
if ( options.fallbackCrs.isValid() )
setCrs( options.fallbackCrs, false );
mWkbType = options.fallbackWkbType;
setProviderType( providerKey );
mGeometryOptions = std::make_unique<QgsGeometryOptions>();
mActions = new QgsActionManager( this );
mConditionalStyles = new QgsConditionalLayerStyles( this );
mStoredExpressionManager = new QgsStoredExpressionManager();
mStoredExpressionManager->setParent( this );
mJoinBuffer = new QgsVectorLayerJoinBuffer( this );
mJoinBuffer->setParent( this );
connect( mJoinBuffer, &QgsVectorLayerJoinBuffer::joinedFieldsChanged, this, &QgsVectorLayer::onJoinedFieldsChanged );
mExpressionFieldBuffer = new QgsExpressionFieldBuffer();
// if we're given a provider type, try to create and bind one to this layer
if ( !vectorLayerPath.isEmpty() && !mProviderKey.isEmpty() )
{
QgsDataProvider::ProviderOptions providerOptions { options.transformContext };
Qgis::DataProviderReadFlags providerFlags;
if ( options.loadDefaultStyle )
{
providerFlags |= Qgis::DataProviderReadFlag::LoadDefaultStyle;
}
if ( options.forceReadOnly )
{
providerFlags |= Qgis::DataProviderReadFlag::ForceReadOnly;
mDataSourceReadOnly = true;
}
setDataSource( vectorLayerPath, baseName, providerKey, providerOptions, providerFlags );
}
for ( const QgsField &field : std::as_const( mFields ) )
{
if ( !mAttributeAliasMap.contains( field.name() ) )
mAttributeAliasMap.insert( field.name(), QString() );
}
if ( isValid() )
{
mTemporalProperties->setDefaultsFromDataProviderTemporalCapabilities( mDataProvider->temporalCapabilities() );
if ( !mTemporalProperties->isActive() )
{
// didn't populate temporal properties from provider metadata, so at least try to setup some initially nice
// selections
mTemporalProperties->guessDefaultsFromFields( mFields );
}
mElevationProperties->setDefaultsFromLayer( this );
}
connect( this, &QgsVectorLayer::selectionChanged, this, [this] { triggerRepaint(); } );
connect( QgsProject::instance()->relationManager(), &QgsRelationManager::relationsLoaded, this, &QgsVectorLayer::onRelationsLoaded ); // skip-keyword-check
connect( this, &QgsVectorLayer::subsetStringChanged, this, &QgsMapLayer::configChanged );
connect( this, &QgsVectorLayer::dataSourceChanged, this, &QgsVectorLayer::supportsEditingChanged );
connect( this, &QgsVectorLayer::readOnlyChanged, this, &QgsVectorLayer::supportsEditingChanged );
// Default simplify drawing settings
QgsSettings settings;
mSimplifyMethod.setSimplifyHints( QgsVectorLayer::settingsSimplifyDrawingHints->valueWithDefaultOverride( mSimplifyMethod.simplifyHints() ) );
mSimplifyMethod.setSimplifyAlgorithm( QgsVectorLayer::settingsSimplifyAlgorithm->valueWithDefaultOverride( mSimplifyMethod.simplifyAlgorithm() ) );
mSimplifyMethod.setThreshold( QgsVectorLayer::settingsSimplifyDrawingTol->valueWithDefaultOverride( mSimplifyMethod.threshold() ) );
mSimplifyMethod.setForceLocalOptimization( QgsVectorLayer::settingsSimplifyLocal->valueWithDefaultOverride( mSimplifyMethod.forceLocalOptimization() ) );
mSimplifyMethod.setMaximumScale( QgsVectorLayer::settingsSimplifyMaxScale->valueWithDefaultOverride( mSimplifyMethod.maximumScale() ) );
connect( mRefreshRendererTimer, &QTimer::timeout, this, [this] { triggerRepaint( true ); } );
}
QgsVectorLayer::~QgsVectorLayer()
{
emit willBeDeleted();
setValid( false );
delete mDataProvider;
delete mEditBuffer;
delete mJoinBuffer;
delete mExpressionFieldBuffer;
delete mLabeling;
delete mDiagramLayerSettings;
delete mDiagramRenderer;
delete mActions;
delete mRenderer;
delete mConditionalStyles;
delete mStoredExpressionManager;
if ( mFeatureCounter )
mFeatureCounter->cancel();
qDeleteAll( mRendererGenerators );
}
QgsVectorLayer *QgsVectorLayer::clone() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QgsVectorLayer::LayerOptions options;
// We get the data source string from the provider when
// possible because some providers may have changed it
// directly (memory provider does that).
QString dataSource;
if ( mDataProvider )
{
dataSource = mDataProvider->dataSourceUri();
options.transformContext = mDataProvider->transformContext();
}
else
{
dataSource = source();
}
options.forceReadOnly = mDataSourceReadOnly;
QgsVectorLayer *layer = new QgsVectorLayer( dataSource, name(), mProviderKey, options );
if ( mDataProvider && layer->dataProvider() )
{
layer->dataProvider()->handlePostCloneOperations( mDataProvider );
}
QgsMapLayer::clone( layer );
layer->mXmlExtent2D = mXmlExtent2D;
layer->mLazyExtent2D = mLazyExtent2D;
layer->mValidExtent2D = mValidExtent2D;
layer->mXmlExtent3D = mXmlExtent3D;
layer->mLazyExtent3D = mLazyExtent3D;
layer->mValidExtent3D = mValidExtent3D;
QList<QgsVectorLayerJoinInfo> joins = vectorJoins();
const auto constJoins = joins;
for ( const QgsVectorLayerJoinInfo &join : constJoins )
{
// do not copy join information for auxiliary layer
if ( !auxiliaryLayer()
|| ( auxiliaryLayer() && auxiliaryLayer()->id() != join.joinLayerId() ) )
layer->addJoin( join );
}
if ( mDataProvider )
layer->setProviderEncoding( mDataProvider->encoding() );
layer->setSubsetString( subsetString() );
layer->setDisplayExpression( displayExpression() );
layer->setMapTipTemplate( mapTipTemplate() );
layer->setMapTipsEnabled( mapTipsEnabled() );
layer->setReadOnly( isReadOnly() );
layer->selectByIds( selectedFeatureIds() );
layer->setAttributeTableConfig( attributeTableConfig() );
layer->setFeatureBlendMode( featureBlendMode() );
layer->setReadExtentFromXml( readExtentFromXml() );
const auto constActions = actions()->actions();
for ( const QgsAction &action : constActions )
{
layer->actions()->addAction( action );
}
if ( auto *lRenderer = renderer() )
{
layer->setRenderer( lRenderer->clone() );
}
if ( auto *lLabeling = labeling() )
{
layer->setLabeling( lLabeling->clone() );
}
layer->setLabelsEnabled( labelsEnabled() );
layer->setSimplifyMethod( simplifyMethod() );
if ( auto *lDiagramRenderer = diagramRenderer() )
{
layer->setDiagramRenderer( lDiagramRenderer->clone() );
}
if ( auto *lDiagramLayerSettings = diagramLayerSettings() )
{
layer->setDiagramLayerSettings( *lDiagramLayerSettings );
}
for ( int i = 0; i < fields().count(); i++ )
{
layer->setFieldAlias( i, attributeAlias( i ) );
layer->setFieldConfigurationFlags( i, fieldConfigurationFlags( i ) );
layer->setEditorWidgetSetup( i, editorWidgetSetup( i ) );
layer->setConstraintExpression( i, constraintExpression( i ), constraintDescription( i ) );
layer->setDefaultValueDefinition( i, defaultValueDefinition( i ) );
QMap< QgsFieldConstraints::Constraint, QgsFieldConstraints::ConstraintStrength> constraints = fieldConstraintsAndStrength( i );
auto constraintIt = constraints.constBegin();
for ( ; constraintIt != constraints.constEnd(); ++ constraintIt )
{
layer->setFieldConstraint( i, constraintIt.key(), constraintIt.value() );
}
if ( fields().fieldOrigin( i ) == Qgis::FieldOrigin::Expression )
{
layer->addExpressionField( expressionField( i ), fields().at( i ) );
}
}
layer->setEditFormConfig( editFormConfig() );
if ( auto *lAuxiliaryLayer = auxiliaryLayer() )
layer->setAuxiliaryLayer( lAuxiliaryLayer->clone( layer ) );
layer->mElevationProperties = mElevationProperties->clone();
layer->mElevationProperties->setParent( layer );
layer->mSelectionProperties = mSelectionProperties->clone();
layer->mSelectionProperties->setParent( layer );
return layer;
}
QString QgsVectorLayer::storageType() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( mDataProvider )
{
return mDataProvider->storageType();
}
return QString();
}
QString QgsVectorLayer::capabilitiesString() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( mDataProvider )
{
return mDataProvider->capabilitiesString();
}
return QString();
}
bool QgsVectorLayer::isSqlQuery() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return mDataProvider && mDataProvider->isSqlQuery();
}
Qgis::VectorLayerTypeFlags QgsVectorLayer::vectorLayerTypeFlags() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return mDataProvider ? mDataProvider->vectorLayerTypeFlags() : Qgis::VectorLayerTypeFlags();
}
QString QgsVectorLayer::dataComment() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( mDataProvider )
{
return mDataProvider->dataComment();
}
return QString();
}
QgsCoordinateReferenceSystem QgsVectorLayer::sourceCrs() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return crs();
}
QString QgsVectorLayer::sourceName() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return name();
}
void QgsVectorLayer::reload()
{
// non fatal for now -- the QgsVirtualLayerTask class is not thread safe and calls this
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
if ( mDataProvider )
{
mDataProvider->reloadData();
updateFields();
}
}
QgsMapLayerRenderer *QgsVectorLayer::createMapRenderer( QgsRenderContext &rendererContext )
{
// non fatal for now -- the "rasterize" processing algorithm is not thread safe and calls this
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
return new QgsVectorLayerRenderer( this, rendererContext );
}
void QgsVectorLayer::drawVertexMarker( double x, double y, QPainter &p, Qgis::VertexMarkerType type, int m )
{
switch ( type )
{
case Qgis::VertexMarkerType::SemiTransparentCircle:
p.setPen( QColor( 50, 100, 120, 200 ) );
p.setBrush( QColor( 200, 200, 210, 120 ) );
p.drawEllipse( x - m, y - m, m * 2 + 1, m * 2 + 1 );
break;
case Qgis::VertexMarkerType::Cross:
p.setPen( QColor( 255, 0, 0 ) );
p.drawLine( x - m, y + m, x + m, y - m );
p.drawLine( x - m, y - m, x + m, y + m );
break;
case Qgis::VertexMarkerType::NoMarker:
break;
}
}
void QgsVectorLayer::select( QgsFeatureId fid )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
mSelectedFeatureIds.insert( fid );
mPreviousSelectedFeatureIds.clear();
emit selectionChanged( QgsFeatureIds() << fid, QgsFeatureIds(), false );
}
void QgsVectorLayer::select( const QgsFeatureIds &featureIds )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
mSelectedFeatureIds.unite( featureIds );
mPreviousSelectedFeatureIds.clear();
emit selectionChanged( featureIds, QgsFeatureIds(), false );
}
void QgsVectorLayer::deselect( const QgsFeatureId fid )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
mSelectedFeatureIds.remove( fid );
mPreviousSelectedFeatureIds.clear();
emit selectionChanged( QgsFeatureIds(), QgsFeatureIds() << fid, false );
}
void QgsVectorLayer::deselect( const QgsFeatureIds &featureIds )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
mSelectedFeatureIds.subtract( featureIds );
mPreviousSelectedFeatureIds.clear();
emit selectionChanged( QgsFeatureIds(), featureIds, false );
}
void QgsVectorLayer::selectByRect( QgsRectangle &rect, Qgis::SelectBehavior behavior )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
// normalize the rectangle
rect.normalize();
QgsFeatureIds newSelection;
QgsFeatureIterator features = getFeatures( QgsFeatureRequest()
.setFilterRect( rect )
.setFlags( Qgis::FeatureRequestFlag::ExactIntersect | Qgis::FeatureRequestFlag::NoGeometry )
.setNoAttributes() );
QgsFeature feat;
while ( features.nextFeature( feat ) )
{
newSelection << feat.id();
}
features.close();
selectByIds( newSelection, behavior );
}
void QgsVectorLayer::selectByExpression( const QString &expression, Qgis::SelectBehavior behavior, QgsExpressionContext *context )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QgsFeatureIds newSelection;
std::optional< QgsExpressionContext > defaultContext;
if ( !context )
{
defaultContext.emplace( QgsExpressionContextUtils::globalProjectLayerScopes( this ) );
context = &defaultContext.value();
}
else
{
context->appendScope( QgsExpressionContextUtils::layerScope( this ) );
}
QgsExpression exp( expression );
exp.prepare( context );
if ( behavior == Qgis::SelectBehavior::SetSelection || behavior == Qgis::SelectBehavior::AddToSelection )
{
QgsFeatureRequest request = QgsFeatureRequest().setFilterExpression( expression )
.setExpressionContext( *context );
request.setSubsetOfAttributes( exp.referencedColumns(), fields() );
if ( !exp.needsGeometry() )
request.setFlags( Qgis::FeatureRequestFlag::NoGeometry );
QgsFeatureIterator features = getFeatures( request );
if ( behavior == Qgis::SelectBehavior::AddToSelection )
{
newSelection = selectedFeatureIds();
}
QgsFeature feat;
while ( features.nextFeature( feat ) )
{
newSelection << feat.id();
}
features.close();
}
else if ( behavior == Qgis::SelectBehavior::IntersectSelection || behavior == Qgis::SelectBehavior::RemoveFromSelection )
{
QgsFeatureIds oldSelection = selectedFeatureIds();
QgsFeatureRequest request = QgsFeatureRequest().setFilterFids( oldSelection );
//refine request
if ( !exp.needsGeometry() )
request.setFlags( Qgis::FeatureRequestFlag::NoGeometry );
request.setSubsetOfAttributes( exp.referencedColumns(), fields() );
QgsFeatureIterator features = getFeatures( request );
QgsFeature feat;
while ( features.nextFeature( feat ) )
{
context->setFeature( feat );
bool matches = exp.evaluate( context ).toBool();
if ( matches && behavior == Qgis::SelectBehavior::IntersectSelection )
{
newSelection << feat.id();
}
else if ( !matches && behavior == Qgis::SelectBehavior::RemoveFromSelection )
{
newSelection << feat.id();
}
}
}
selectByIds( newSelection );
}
void QgsVectorLayer::selectByIds( const QgsFeatureIds &ids, Qgis::SelectBehavior behavior )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QgsFeatureIds newSelection;
switch ( behavior )
{
case Qgis::SelectBehavior::SetSelection:
newSelection = ids;
break;
case Qgis::SelectBehavior::AddToSelection:
newSelection = mSelectedFeatureIds + ids;
break;
case Qgis::SelectBehavior::RemoveFromSelection:
newSelection = mSelectedFeatureIds - ids;
break;
case Qgis::SelectBehavior::IntersectSelection:
newSelection = mSelectedFeatureIds.intersect( ids );
break;
}
QgsFeatureIds deselectedFeatures = mSelectedFeatureIds - newSelection;
mSelectedFeatureIds = newSelection;
mPreviousSelectedFeatureIds.clear();
emit selectionChanged( newSelection, deselectedFeatures, true );
}
void QgsVectorLayer::modifySelection( const QgsFeatureIds &selectIds, const QgsFeatureIds &deselectIds )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QgsFeatureIds intersectingIds = selectIds & deselectIds;
if ( !intersectingIds.isEmpty() )
{
QgsDebugMsgLevel( QStringLiteral( "Trying to select and deselect the same item at the same time. Unsure what to do. Selecting dubious items." ), 3 );
}
mSelectedFeatureIds -= deselectIds;
mSelectedFeatureIds += selectIds;
mPreviousSelectedFeatureIds.clear();
emit selectionChanged( selectIds, deselectIds - intersectingIds, false );
}
void QgsVectorLayer::invertSelection()
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QgsFeatureIds ids = allFeatureIds();
ids.subtract( mSelectedFeatureIds );
selectByIds( ids );
}
void QgsVectorLayer::selectAll()
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
selectByIds( allFeatureIds() );
}
void QgsVectorLayer::invertSelectionInRectangle( QgsRectangle &rect )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
// normalize the rectangle
rect.normalize();
QgsFeatureIterator fit = getFeatures( QgsFeatureRequest()
.setFilterRect( rect )
.setFlags( Qgis::FeatureRequestFlag::NoGeometry | Qgis::FeatureRequestFlag::ExactIntersect )
.setNoAttributes() );
QgsFeatureIds selectIds;
QgsFeatureIds deselectIds;
QgsFeature fet;
while ( fit.nextFeature( fet ) )
{
if ( mSelectedFeatureIds.contains( fet.id() ) )
{
deselectIds << fet.id();
}
else
{
selectIds << fet.id();
}
}
modifySelection( selectIds, deselectIds );
}
void QgsVectorLayer::removeSelection()
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( mSelectedFeatureIds.isEmpty() )
return;
const QgsFeatureIds previous = mSelectedFeatureIds;
selectByIds( QgsFeatureIds() );
mPreviousSelectedFeatureIds = previous;
}
void QgsVectorLayer::reselect()
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( mPreviousSelectedFeatureIds.isEmpty() || !mSelectedFeatureIds.empty() )
return;
selectByIds( mPreviousSelectedFeatureIds );
}
QgsVectorDataProvider *QgsVectorLayer::dataProvider()
{
// non fatal for now -- the "rasterize" processing algorithm is not thread safe and calls this
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
return mDataProvider;
}
const QgsVectorDataProvider *QgsVectorLayer::dataProvider() const
{
// non fatal for now -- the "rasterize" processing algorithm is not thread safe and calls this
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
return mDataProvider;
}
QgsMapLayerSelectionProperties *QgsVectorLayer::selectionProperties()
{
// non fatal for now -- the "rasterize" processing algorithm is not thread safe and calls this
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
return mSelectionProperties;
}
QgsMapLayerTemporalProperties *QgsVectorLayer::temporalProperties()
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return mTemporalProperties;
}
QgsMapLayerElevationProperties *QgsVectorLayer::elevationProperties()
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return mElevationProperties;
}
QgsAbstractProfileGenerator *QgsVectorLayer::createProfileGenerator( const QgsProfileRequest &request )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QgsProfileRequest modifiedRequest( request );
modifiedRequest.expressionContext().appendScope( createExpressionContextScope() );
return new QgsVectorLayerProfileGenerator( this, modifiedRequest );
}
void QgsVectorLayer::setProviderEncoding( const QString &encoding )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( isValid() && mDataProvider && mDataProvider->encoding() != encoding )
{
mDataProvider->setEncoding( encoding );
updateFields();
}
}
void QgsVectorLayer::setDiagramRenderer( QgsDiagramRenderer *r )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
delete mDiagramRenderer;
mDiagramRenderer = r;
emit rendererChanged();
emit styleChanged();
}
Qgis::GeometryType QgsVectorLayer::geometryType() const
{
// non fatal for now -- the "rasterize" processing algorithm is not thread safe and calls this
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
return QgsWkbTypes::geometryType( mWkbType );
}
Qgis::WkbType QgsVectorLayer::wkbType() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return mWkbType;
}
QgsRectangle QgsVectorLayer::boundingBoxOfSelected() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !isValid() || !isSpatial() || mSelectedFeatureIds.isEmpty() || !mDataProvider ) //no selected features
{
return QgsRectangle( 0, 0, 0, 0 );
}
QgsRectangle r, retval;
retval.setNull();
QgsFeature fet;
if ( mDataProvider->capabilities() & Qgis::VectorProviderCapability::SelectAtId )
{
QgsFeatureIterator fit = getFeatures( QgsFeatureRequest()
.setFilterFids( mSelectedFeatureIds )
.setNoAttributes() );
while ( fit.nextFeature( fet ) )
{
if ( !fet.hasGeometry() )
continue;
r = fet.geometry().boundingBox();
retval.combineExtentWith( r );
}
}
else
{
QgsFeatureIterator fit = getFeatures( QgsFeatureRequest()
.setNoAttributes() );
while ( fit.nextFeature( fet ) )
{
if ( mSelectedFeatureIds.contains( fet.id() ) )
{
if ( fet.hasGeometry() )
{
r = fet.geometry().boundingBox();
retval.combineExtentWith( r );
}
}
}
}
if ( retval.width() == 0.0 || retval.height() == 0.0 )
{
// If all of the features are at the one point, buffer the
// rectangle a bit. If they are all at zero, do something a bit
// more crude.
if ( retval.xMinimum() == 0.0 && retval.xMaximum() == 0.0 &&
retval.yMinimum() == 0.0 && retval.yMaximum() == 0.0 )
{
retval.set( -1.0, -1.0, 1.0, 1.0 );
}
}
return retval;
}
bool QgsVectorLayer::labelsEnabled() const
{
// non fatal for now -- the "rasterize" processing algorithm is not thread safe and calls this
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
return mLabelsEnabled && static_cast< bool >( mLabeling );
}
void QgsVectorLayer::setLabelsEnabled( bool enabled )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
mLabelsEnabled = enabled;
}
bool QgsVectorLayer::diagramsEnabled() const
{
// non fatal for now -- the "rasterize" processing algorithm is not thread safe and calls this
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
if ( !mDiagramRenderer || !mDiagramLayerSettings )
return false;
QList<QgsDiagramSettings> settingList = mDiagramRenderer->diagramSettings();
if ( !settingList.isEmpty() )
{
return settingList.at( 0 ).enabled;
}
return false;
}
long long QgsVectorLayer::featureCount( const QString &legendKey ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !mSymbolFeatureCounted )
return -1;
return mSymbolFeatureCountMap.value( legendKey, -1 );
}
QgsFeatureIds QgsVectorLayer::symbolFeatureIds( const QString &legendKey ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !mSymbolFeatureCounted )
return QgsFeatureIds();
return mSymbolFeatureIdMap.value( legendKey, QgsFeatureIds() );
}
QgsVectorLayerFeatureCounter *QgsVectorLayer::countSymbolFeatures( bool storeSymbolFids )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( ( mSymbolFeatureCounted || mFeatureCounter ) && !( storeSymbolFids && mSymbolFeatureIdMap.isEmpty() ) )
return mFeatureCounter;
mSymbolFeatureCountMap.clear();
mSymbolFeatureIdMap.clear();
if ( !isValid() )
{
QgsDebugMsgLevel( QStringLiteral( "invoked with invalid layer" ), 3 );
return mFeatureCounter;
}
if ( !mDataProvider )
{
QgsDebugMsgLevel( QStringLiteral( "invoked with null mDataProvider" ), 3 );
return mFeatureCounter;
}
if ( !mRenderer )
{
QgsDebugMsgLevel( QStringLiteral( "invoked with null mRenderer" ), 3 );
return mFeatureCounter;
}
if ( !mFeatureCounter || ( storeSymbolFids && mSymbolFeatureIdMap.isEmpty() ) )
{
mFeatureCounter = new QgsVectorLayerFeatureCounter( this, QgsExpressionContext(), storeSymbolFids );
connect( mFeatureCounter, &QgsTask::taskCompleted, this, &QgsVectorLayer::onFeatureCounterCompleted, Qt::UniqueConnection );
connect( mFeatureCounter, &QgsTask::taskTerminated, this, &QgsVectorLayer::onFeatureCounterTerminated, Qt::UniqueConnection );
QgsApplication::taskManager()->addTask( mFeatureCounter );
}
return mFeatureCounter;
}
void QgsVectorLayer::updateExtents( bool force )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
// do not update extent by default when trust project option is activated
if ( force || !mReadExtentFromXml || ( mReadExtentFromXml && mXmlExtent2D.isNull() && mXmlExtent3D.isNull() ) )
{
mValidExtent2D = false;
mValidExtent3D = false;
}
}
void QgsVectorLayer::setExtent( const QgsRectangle &r )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QgsMapLayer::setExtent( r );
mValidExtent2D = true;
}
void QgsVectorLayer::setExtent3D( const QgsBox3D &r )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QgsMapLayer::setExtent3D( r );
mValidExtent3D = true;
}
void QgsVectorLayer::updateDefaultValues( QgsFeatureId fid, QgsFeature feature, QgsExpressionContext *context )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !mDefaultValueOnUpdateFields.isEmpty() )
{
if ( !feature.isValid() )
feature = getFeature( fid );
int size = mFields.size();
for ( int idx : std::as_const( mDefaultValueOnUpdateFields ) )
{
if ( idx < 0 || idx >= size )
continue;
feature.setAttribute( idx, defaultValue( idx, feature, context ) );
updateFeature( feature, true );
}
}
}
QgsRectangle QgsVectorLayer::extent() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QgsRectangle rect;
rect.setNull();
if ( !isSpatial() )
return rect;
// Don't do lazy extent if the layer is currently in edit mode
if ( mLazyExtent2D && isEditable() )
mLazyExtent2D = false;
if ( mDataProvider && mDataProvider->isValid() && ( mDataProvider->flags() & Qgis::DataProviderFlag::FastExtent2D ) )
{
// Provider has a trivial 2D extent calculation => always get extent from provider.
// Things are nice and simple this way, e.g. we can always trust that this extent is
// accurate and up to date.
updateExtent( mDataProvider->extent() );
mValidExtent2D = true;
mLazyExtent2D = false;
}
else
{
if ( !mValidExtent2D && mLazyExtent2D && mReadExtentFromXml && !mXmlExtent2D.isNull() )
{
updateExtent( mXmlExtent2D );
mValidExtent2D = true;
mLazyExtent2D = false;
}
if ( !mValidExtent2D && mLazyExtent2D && mDataProvider && mDataProvider->isValid() )
{
// store the extent
updateExtent( mDataProvider->extent() );
mValidExtent2D = true;
mLazyExtent2D = false;
// show the extent
QgsDebugMsgLevel( QStringLiteral( "2D Extent of layer: %1" ).arg( mExtent2D.toString() ), 3 );
}
}
if ( mValidExtent2D )
return QgsMapLayer::extent();
if ( !isValid() || !mDataProvider )
{
QgsDebugMsgLevel( QStringLiteral( "invoked with invalid layer or null mDataProvider" ), 3 );
return rect;
}
if ( !mEditBuffer ||
( !mDataProvider->transaction() && ( mEditBuffer->deletedFeatureIds().isEmpty() && mEditBuffer->changedGeometries().isEmpty() ) ) ||
QgsDataSourceUri( mDataProvider->dataSourceUri() ).useEstimatedMetadata() )
{
mDataProvider->updateExtents();
// get the extent of the layer from the provider
// but only when there are some features already
if ( mDataProvider->featureCount() != 0 )
{
const QgsRectangle r = mDataProvider->extent();
rect.combineExtentWith( r );
}
if ( mEditBuffer && !mDataProvider->transaction() )
{
const auto addedFeatures = mEditBuffer->addedFeatures();
for ( QgsFeatureMap::const_iterator it = addedFeatures.constBegin(); it != addedFeatures.constEnd(); ++it )
{
if ( it->hasGeometry() )
{
const QgsRectangle r = it->geometry().boundingBox();
rect.combineExtentWith( r );
}
}
}
}
else
{
QgsFeatureIterator fit = getFeatures( QgsFeatureRequest()
.setNoAttributes() );
QgsFeature fet;
while ( fit.nextFeature( fet ) )
{
if ( fet.hasGeometry() && fet.geometry().type() != Qgis::GeometryType::Unknown )
{
const QgsRectangle bb = fet.geometry().boundingBox();
rect.combineExtentWith( bb );
}
}
}
if ( rect.xMinimum() > rect.xMaximum() && rect.yMinimum() > rect.yMaximum() )
{
// special case when there are no features in provider nor any added
rect = QgsRectangle(); // use rectangle with zero coordinates
}
updateExtent( rect );
mValidExtent2D = true;
// Send this (hopefully) up the chain to the map canvas
emit recalculateExtents();
return rect;
}
QgsBox3D QgsVectorLayer:: extent3D() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
// if data is 2D, redirect to 2D extend computation, and save it as 2D extent (in 3D bbox)
if ( mDataProvider && mDataProvider->elevationProperties() && !mDataProvider->elevationProperties()->containsElevationData() )
{
return QgsBox3D( extent() );
}
QgsBox3D extent;
extent.setNull();
if ( !isSpatial() )
return extent;
if ( mDataProvider && mDataProvider->isValid() && ( mDataProvider->flags() & Qgis::DataProviderFlag::FastExtent3D ) )
{
// Provider has a trivial 3D extent calculation => always get extent from provider.
// Things are nice and simple this way, e.g. we can always trust that this extent is
// accurate and up to date.
updateExtent( mDataProvider->extent3D() );
mValidExtent3D = true;
mLazyExtent3D = false;
}
else
{
if ( !mValidExtent3D && mLazyExtent3D && mReadExtentFromXml && !mXmlExtent3D.isNull() )
{
updateExtent( mXmlExtent3D );
mValidExtent3D = true;
mLazyExtent3D = false;
}
if ( !mValidExtent3D && mLazyExtent3D && mDataProvider && mDataProvider->isValid() )
{
// store the extent
updateExtent( mDataProvider->extent3D() );
mValidExtent3D = true;
mLazyExtent3D = false;
// show the extent
QgsDebugMsgLevel( QStringLiteral( "3D Extent of layer: %1" ).arg( mExtent3D.toString() ), 3 );
}
}
if ( mValidExtent3D )
return QgsMapLayer::extent3D();
if ( !isValid() || !mDataProvider )
{
QgsDebugMsgLevel( QStringLiteral( "invoked with invalid layer or null mDataProvider" ), 3 );
return extent;
}
if ( !mEditBuffer ||
( !mDataProvider->transaction() && ( mEditBuffer->deletedFeatureIds().isEmpty() && mEditBuffer->changedGeometries().isEmpty() ) ) ||
QgsDataSourceUri( mDataProvider->dataSourceUri() ).useEstimatedMetadata() )
{
mDataProvider->updateExtents();
// get the extent of the layer from the provider
// but only when there are some features already
if ( mDataProvider->featureCount() != 0 )
{
const QgsBox3D ext = mDataProvider->extent3D();
extent.combineWith( ext );
}
if ( mEditBuffer && !mDataProvider->transaction() )
{
const auto addedFeatures = mEditBuffer->addedFeatures();
for ( QgsFeatureMap::const_iterator it = addedFeatures.constBegin(); it != addedFeatures.constEnd(); ++it )
{
if ( it->hasGeometry() )
{
const QgsBox3D bbox = it->geometry().boundingBox3D();
extent.combineWith( bbox );
}
}
}
}
else
{
QgsFeatureIterator fit = getFeatures( QgsFeatureRequest()
.setNoAttributes() );
QgsFeature fet;
while ( fit.nextFeature( fet ) )
{
if ( fet.hasGeometry() && fet.geometry().type() != Qgis::GeometryType::Unknown )
{
const QgsBox3D bb = fet.geometry().boundingBox3D();
extent.combineWith( bb );
}
}
}
if ( extent.xMinimum() > extent.xMaximum() && extent.yMinimum() > extent.yMaximum() && extent.zMinimum() > extent.zMaximum() )
{
// special case when there are no features in provider nor any added
extent = QgsBox3D(); // use rectangle with zero coordinates
}
updateExtent( extent );
mValidExtent3D = true;
// Send this (hopefully) up the chain to the map canvas
emit recalculateExtents();
return extent;
}
QgsRectangle QgsVectorLayer::sourceExtent() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return extent();
}
QgsBox3D QgsVectorLayer::sourceExtent3D() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return extent3D();
}
QString QgsVectorLayer::subsetString() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !isValid() || !mDataProvider )
{
QgsDebugMsgLevel( QStringLiteral( "invoked with invalid layer or null mDataProvider" ), 3 );
return customProperty( QStringLiteral( "storedSubsetString" ) ).toString();
}
return mDataProvider->subsetString();
}
bool QgsVectorLayer::setSubsetString( const QString &subset )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !isValid() || !mDataProvider )
{
QgsDebugMsgLevel( QStringLiteral( "invoked with invalid layer or null mDataProvider or while editing" ), 3 );
setCustomProperty( QStringLiteral( "storedSubsetString" ), subset );
return false;
}
else if ( mEditBuffer )
{
QgsDebugMsgLevel( QStringLiteral( "invoked while editing" ), 3 );
return false;
}
if ( subset == mDataProvider->subsetString() )
return true;
bool res = mDataProvider->setSubsetString( subset );
// get the updated data source string from the provider
mDataSource = mDataProvider->dataSourceUri();
updateExtents();
updateFields();
if ( res )
{
emit subsetStringChanged();
triggerRepaint();
}
return res;
}
bool QgsVectorLayer::simplifyDrawingCanbeApplied( const QgsRenderContext &renderContext, Qgis::VectorRenderingSimplificationFlag simplifyHint ) const
{
// non fatal for now -- the "rasterize" processing algorithm is not thread safe and calls this
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
if ( isValid() && mDataProvider && !mEditBuffer && ( isSpatial() && geometryType() != Qgis::GeometryType::Point ) && ( mSimplifyMethod.simplifyHints() & simplifyHint ) && renderContext.useRenderingOptimization() )
{
double maximumSimplificationScale = mSimplifyMethod.maximumScale();
// check maximum scale at which generalisation should be carried out
return !( maximumSimplificationScale > 1 && renderContext.rendererScale() <= maximumSimplificationScale );
}
return false;
}
QgsConditionalLayerStyles *QgsVectorLayer::conditionalStyles() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return mConditionalStyles;
}
QgsFeatureIterator QgsVectorLayer::getFeatures( const QgsFeatureRequest &request ) const
{
// non fatal for now -- the aggregate expression functions are not thread safe and call this
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
if ( !isValid() || !mDataProvider )
return QgsFeatureIterator();
return QgsFeatureIterator( new QgsVectorLayerFeatureIterator( new QgsVectorLayerFeatureSource( this ), true, request ) );
}
QgsGeometry QgsVectorLayer::getGeometry( QgsFeatureId fid ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QgsFeature feature;
getFeatures( QgsFeatureRequest( fid ).setFlags( Qgis::FeatureRequestFlag::SubsetOfAttributes ) ).nextFeature( feature );
if ( feature.isValid() )
return feature.geometry();
else
return QgsGeometry();
}
bool QgsVectorLayer::addFeature( QgsFeature &feature, Flags )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !isValid() || !mEditBuffer || !mDataProvider )
return false;
if ( mGeometryOptions->isActive() )
{
QgsGeometry geom = feature.geometry();
mGeometryOptions->apply( geom );
feature.setGeometry( geom );
}
bool success = mEditBuffer->addFeature( feature );
if ( success && mJoinBuffer->containsJoins() )
{
success = mJoinBuffer->addFeature( feature );
}
return success;
}
bool QgsVectorLayer::updateFeature( QgsFeature &updatedFeature, bool skipDefaultValues )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !mEditBuffer || !mDataProvider )
{
return false;
}
QgsFeature currentFeature = getFeature( updatedFeature.id() );
if ( currentFeature.isValid() )
{
bool hasChanged = false;
bool hasError = false;
if ( ( updatedFeature.hasGeometry() || currentFeature.hasGeometry() ) && !updatedFeature.geometry().equals( currentFeature.geometry() ) )
{
QgsGeometry geometry = updatedFeature.geometry();
if ( changeGeometry( updatedFeature.id(), geometry, true ) )
{
hasChanged = true;
updatedFeature.setGeometry( geometry );
}
else
{
QgsDebugMsgLevel( QStringLiteral( "geometry of feature %1 could not be changed." ).arg( updatedFeature.id() ), 3 );
}
}
QgsAttributes fa = updatedFeature.attributes();
QgsAttributes ca = currentFeature.attributes();
for ( int attr = 0; attr < fa.count(); ++attr )
{
if ( !qgsVariantEqual( fa.at( attr ), ca.at( attr ) ) )
{
if ( changeAttributeValue( updatedFeature.id(), attr, fa.at( attr ), ca.at( attr ), true ) )
{
hasChanged = true;
}
else
{
QgsDebugMsgLevel( QStringLiteral( "attribute %1 of feature %2 could not be changed." ).arg( attr ).arg( updatedFeature.id() ), 3 );
hasError = true;
}
}
}
if ( hasChanged && !mDefaultValueOnUpdateFields.isEmpty() && !skipDefaultValues )
updateDefaultValues( updatedFeature.id(), updatedFeature );
return !hasError;
}
else
{
QgsDebugMsgLevel( QStringLiteral( "feature %1 could not be retrieved" ).arg( updatedFeature.id() ), 3 );
return false;
}
}
bool QgsVectorLayer::insertVertex( double x, double y, QgsFeatureId atFeatureId, int beforeVertex )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !isValid() || !mEditBuffer || !mDataProvider )
return false;
QgsVectorLayerEditUtils utils( this );
bool result = utils.insertVertex( x, y, atFeatureId, beforeVertex );
if ( result )
updateExtents();
return result;
}
bool QgsVectorLayer::insertVertex( const QgsPoint &point, QgsFeatureId atFeatureId, int beforeVertex )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !isValid() || !mEditBuffer || !mDataProvider )
return false;
QgsVectorLayerEditUtils utils( this );
bool result = utils.insertVertex( point, atFeatureId, beforeVertex );
if ( result )
updateExtents();
return result;
}
bool QgsVectorLayer::moveVertex( double x, double y, QgsFeatureId atFeatureId, int atVertex )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !isValid() || !mEditBuffer || !mDataProvider )
return false;
QgsVectorLayerEditUtils utils( this );
bool result = utils.moveVertex( x, y, atFeatureId, atVertex );
if ( result )
updateExtents();
return result;
}
bool QgsVectorLayer::moveVertex( const QgsPoint &p, QgsFeatureId atFeatureId, int atVertex )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !isValid() || !mEditBuffer || !mDataProvider )
return false;
QgsVectorLayerEditUtils utils( this );
bool result = utils.moveVertex( p, atFeatureId, atVertex );
if ( result )
updateExtents();
return result;
}
Qgis::VectorEditResult QgsVectorLayer::deleteVertex( QgsFeatureId featureId, int vertex )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !isValid() || !mEditBuffer || !mDataProvider )
return Qgis::VectorEditResult::InvalidLayer;
QgsVectorLayerEditUtils utils( this );
Qgis::VectorEditResult result = utils.deleteVertex( featureId, vertex );
if ( result == Qgis::VectorEditResult::Success )
updateExtents();
return result;
}
bool QgsVectorLayer::deleteSelectedFeatures( int *deletedCount, QgsVectorLayer::DeleteContext *context )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !isValid() || !mDataProvider || !( mDataProvider->capabilities() & Qgis::VectorProviderCapability::DeleteFeatures ) )
{
return false;
}
if ( !isEditable() )
{
return false;
}
int deleted = 0;
int count = mSelectedFeatureIds.size();
// Make a copy since deleteFeature modifies mSelectedFeatureIds
QgsFeatureIds selectedFeatures( mSelectedFeatureIds );
for ( QgsFeatureId fid : std::as_const( selectedFeatures ) )
{
deleted += deleteFeature( fid, context ); // removes from selection
}
triggerRepaint();
updateExtents();
if ( deletedCount )
{
*deletedCount = deleted;
}
return deleted == count;
}
static const QgsPointSequence vectorPointXY2pointSequence( const QVector<QgsPointXY> &points )
{
QgsPointSequence pts;
pts.reserve( points.size() );
QVector<QgsPointXY>::const_iterator it = points.constBegin();
while ( it != points.constEnd() )
{
pts.append( QgsPoint( *it ) );
++it;
}
return pts;
}
Qgis::GeometryOperationResult QgsVectorLayer::addRing( const QVector<QgsPointXY> &ring, QgsFeatureId *featureId )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return addRing( vectorPointXY2pointSequence( ring ), featureId );
}
Qgis::GeometryOperationResult QgsVectorLayer::addRing( const QgsPointSequence &ring, QgsFeatureId *featureId )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !isValid() || !mEditBuffer || !mDataProvider )
return Qgis::GeometryOperationResult::LayerNotEditable;
QgsVectorLayerEditUtils utils( this );
Qgis::GeometryOperationResult result = Qgis::GeometryOperationResult::AddRingNotInExistingFeature;
//first try with selected features
if ( !mSelectedFeatureIds.isEmpty() )
{
result = utils.addRing( ring, mSelectedFeatureIds, featureId );
}
if ( result != Qgis::GeometryOperationResult::Success )
{
//try with all intersecting features
result = utils.addRing( ring, QgsFeatureIds(), featureId );
}
return result;
}
Qgis::GeometryOperationResult QgsVectorLayer::addRing( QgsCurve *ring, QgsFeatureId *featureId )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !isValid() || !mEditBuffer || !mDataProvider )
{
delete ring;
return Qgis::GeometryOperationResult::LayerNotEditable;
}
if ( !ring )
{
return Qgis::GeometryOperationResult::InvalidInputGeometryType;
}
if ( !ring->isClosed() )
{
delete ring;
return Qgis::GeometryOperationResult::AddRingNotClosed;
}
QgsVectorLayerEditUtils utils( this );
Qgis::GeometryOperationResult result = Qgis::GeometryOperationResult::AddRingNotInExistingFeature;
//first try with selected features
if ( !mSelectedFeatureIds.isEmpty() )
{
result = utils.addRing( static_cast< QgsCurve * >( ring->clone() ), mSelectedFeatureIds, featureId );
}
if ( result != Qgis::GeometryOperationResult::Success )
{
//try with all intersecting features
result = utils.addRing( static_cast< QgsCurve * >( ring->clone() ), QgsFeatureIds(), featureId );
}
delete ring;
return result;
}
Qgis::GeometryOperationResult QgsVectorLayer::addPart( const QList<QgsPointXY> &points )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QgsPointSequence pts;
pts.reserve( points.size() );
for ( QList<QgsPointXY>::const_iterator it = points.constBegin(); it != points.constEnd() ; ++it )
{
pts.append( QgsPoint( *it ) );
}
return addPart( pts );
}
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
Qgis::GeometryOperationResult QgsVectorLayer::addPart( const QVector<QgsPointXY> &points )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return addPart( vectorPointXY2pointSequence( points ) );
}
#endif
Qgis::GeometryOperationResult QgsVectorLayer::addPart( const QgsPointSequence &points )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !isValid() || !mEditBuffer || !mDataProvider )
return Qgis::GeometryOperationResult::LayerNotEditable;
//number of selected features must be 1
if ( mSelectedFeatureIds.empty() )
{
QgsDebugMsgLevel( QStringLiteral( "Number of selected features <1" ), 3 );
return Qgis::GeometryOperationResult::SelectionIsEmpty;
}
else if ( mSelectedFeatureIds.size() > 1 )
{
QgsDebugMsgLevel( QStringLiteral( "Number of selected features >1" ), 3 );
return Qgis::GeometryOperationResult::SelectionIsGreaterThanOne;
}
QgsVectorLayerEditUtils utils( this );
Qgis::GeometryOperationResult result = utils.addPart( points, *mSelectedFeatureIds.constBegin() );
if ( result == Qgis::GeometryOperationResult::Success )
updateExtents();
return result;
}
Qgis::GeometryOperationResult QgsVectorLayer::addPart( QgsCurve *ring )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !isValid() || !mEditBuffer || !mDataProvider )
return Qgis::GeometryOperationResult::LayerNotEditable;
//number of selected features must be 1
if ( mSelectedFeatureIds.empty() )
{
QgsDebugMsgLevel( QStringLiteral( "Number of selected features <1" ), 3 );
return Qgis::GeometryOperationResult::SelectionIsEmpty;
}
else if ( mSelectedFeatureIds.size() > 1 )
{
QgsDebugMsgLevel( QStringLiteral( "Number of selected features >1" ), 3 );
return Qgis::GeometryOperationResult::SelectionIsGreaterThanOne;
}
QgsVectorLayerEditUtils utils( this );
Qgis::GeometryOperationResult result = utils.addPart( ring, *mSelectedFeatureIds.constBegin() );
if ( result == Qgis::GeometryOperationResult::Success )
updateExtents();
return result;
}
// TODO QGIS 4.0 -- this should return Qgis::GeometryOperationResult, not int
int QgsVectorLayer::translateFeature( QgsFeatureId featureId, double dx, double dy )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !isValid() || !mEditBuffer || !mDataProvider )
return static_cast< int >( Qgis::GeometryOperationResult::LayerNotEditable );
QgsVectorLayerEditUtils utils( this );
int result = utils.translateFeature( featureId, dx, dy );
if ( result == static_cast< int >( Qgis::GeometryOperationResult::Success ) )
updateExtents();
return result;
}
Qgis::GeometryOperationResult QgsVectorLayer::splitParts( const QVector<QgsPointXY> &splitLine, bool topologicalEditing )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return splitParts( vectorPointXY2pointSequence( splitLine ), topologicalEditing );
}
Qgis::GeometryOperationResult QgsVectorLayer::splitParts( const QgsPointSequence &splitLine, bool topologicalEditing )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !isValid() || !mEditBuffer || !mDataProvider )
return Qgis::GeometryOperationResult::LayerNotEditable;
QgsVectorLayerEditUtils utils( this );
return utils.splitParts( splitLine, topologicalEditing );
}
Qgis::GeometryOperationResult QgsVectorLayer::splitFeatures( const QVector<QgsPointXY> &splitLine, bool topologicalEditing )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return splitFeatures( vectorPointXY2pointSequence( splitLine ), topologicalEditing );
}
Qgis::GeometryOperationResult QgsVectorLayer::splitFeatures( const QgsPointSequence &splitLine, bool topologicalEditing )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QgsLineString splitLineString( splitLine );
QgsPointSequence topologyTestPoints;
bool preserveCircular = false;
return splitFeatures( &splitLineString, topologyTestPoints, preserveCircular, topologicalEditing );
}
Qgis::GeometryOperationResult QgsVectorLayer::splitFeatures( const QgsCurve *curve, QgsPointSequence &topologyTestPoints, bool preserveCircular, bool topologicalEditing )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !isValid() || !mEditBuffer || !mDataProvider )
return Qgis::GeometryOperationResult::LayerNotEditable;
QgsVectorLayerEditUtils utils( this );
return utils.splitFeatures( curve, topologyTestPoints, preserveCircular, topologicalEditing );
}
int QgsVectorLayer::addTopologicalPoints( const QgsGeometry &geom )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !isValid() || !mEditBuffer || !mDataProvider )
return -1;
QgsVectorLayerEditUtils utils( this );
return utils.addTopologicalPoints( geom );
}
int QgsVectorLayer::addTopologicalPoints( const QgsPointXY &p )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return addTopologicalPoints( QgsPoint( p ) );
}
int QgsVectorLayer::addTopologicalPoints( const QgsPoint &p )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !isValid() || !mEditBuffer || !mDataProvider )
return -1;
QgsVectorLayerEditUtils utils( this );
return utils.addTopologicalPoints( p );
}
int QgsVectorLayer::addTopologicalPoints( const QgsPointSequence &ps )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !mValid || !mEditBuffer || !mDataProvider )
return -1;
QgsVectorLayerEditUtils utils( this );
return utils.addTopologicalPoints( ps );
}
void QgsVectorLayer::setLabeling( QgsAbstractVectorLayerLabeling *labeling )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( mLabeling == labeling )
return;
delete mLabeling;
mLabeling = labeling;
}
bool QgsVectorLayer::startEditing()
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( project() && project()->transactionMode() == Qgis::TransactionMode::BufferedGroups )
return project()->startEditing( this );
if ( !isValid() || !mDataProvider )
{
return false;
}
// allow editing if provider supports any of the capabilities
if ( !supportsEditing() )
{
return false;
}
if ( mEditBuffer )
{
// editing already underway
return false;
}
mDataProvider->enterUpdateMode();
emit beforeEditingStarted();
createEditBuffer();
updateFields();
emit editingStarted();
return true;
}
void QgsVectorLayer::setTransformContext( const QgsCoordinateTransformContext &transformContext )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( mDataProvider )
mDataProvider->setTransformContext( transformContext );
}
Qgis::SpatialIndexPresence QgsVectorLayer::hasSpatialIndex() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return mDataProvider ? mDataProvider->hasSpatialIndex() : Qgis::SpatialIndexPresence::Unknown;
}
bool QgsVectorLayer::accept( QgsStyleEntityVisitorInterface *visitor ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( mRenderer )
if ( !mRenderer->accept( visitor ) )
return false;
if ( mLabeling )
if ( !mLabeling->accept( visitor ) )
return false;
return true;
}
bool QgsVectorLayer::readXml( const QDomNode &layer_node, QgsReadWriteContext &context )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QgsDebugMsgLevel( QStringLiteral( "Datasource in QgsVectorLayer::readXml: %1" ).arg( mDataSource.toLocal8Bit().data() ), 3 );
//process provider key
QDomNode pkeyNode = layer_node.namedItem( QStringLiteral( "provider" ) );
if ( pkeyNode.isNull() )
{
mProviderKey.clear();
}
else
{
QDomElement pkeyElt = pkeyNode.toElement();
mProviderKey = pkeyElt.text();
}
// determine type of vector layer
if ( !mProviderKey.isNull() )
{
// if the provider string isn't empty, then we successfully
// got the stored provider
}
else if ( mDataSource.contains( QLatin1String( "dbname=" ) ) )
{
mProviderKey = QStringLiteral( "postgres" );
}
else
{
mProviderKey = QStringLiteral( "ogr" );
}
const QDomElement elem = layer_node.toElement();
QgsDataProvider::ProviderOptions options { context.transformContext() };
mDataSourceReadOnly = mReadFlags & QgsMapLayer::FlagForceReadOnly;
Qgis::DataProviderReadFlags flags = providerReadFlags( layer_node, mReadFlags );
if ( ( mReadFlags & QgsMapLayer::FlagDontResolveLayers ) || !setDataProvider( mProviderKey, options, flags ) )
{
if ( !( mReadFlags & QgsMapLayer::FlagDontResolveLayers ) )
{
QgsDebugError( QStringLiteral( "Could not set data provider for layer %1" ).arg( publicSource() ) );
}
// for invalid layer sources, we fallback to stored wkbType if available
if ( elem.hasAttribute( QStringLiteral( "wkbType" ) ) )
mWkbType = qgsEnumKeyToValue( elem.attribute( QStringLiteral( "wkbType" ) ), mWkbType );
}
QDomElement pkeyElem = pkeyNode.toElement();
if ( !pkeyElem.isNull() )
{
QString encodingString = pkeyElem.attribute( QStringLiteral( "encoding" ) );
if ( mDataProvider && !encodingString.isEmpty() )
{
mDataProvider->setEncoding( encodingString );
}
}
// load vector joins - does not resolve references to layers yet
mJoinBuffer->readXml( layer_node );
updateFields();
// If style doesn't include a legend, we'll need to make a default one later...
mSetLegendFromStyle = false;
QString errorMsg;
if ( !readSymbology( layer_node, errorMsg, context ) )
{
return false;
}
readStyleManager( layer_node );
QDomNode depsNode = layer_node.namedItem( QStringLiteral( "dataDependencies" ) );
QDomNodeList depsNodes = depsNode.childNodes();
QSet<QgsMapLayerDependency> sources;
for ( int i = 0; i < depsNodes.count(); i++ )
{
QString source = depsNodes.at( i ).toElement().attribute( QStringLiteral( "id" ) );
sources << QgsMapLayerDependency( source );
}
setDependencies( sources );
if ( !mSetLegendFromStyle )
setLegend( QgsMapLayerLegend::defaultVectorLegend( this ) );
// read extent
if ( mReadFlags & QgsMapLayer::FlagReadExtentFromXml )
{
mReadExtentFromXml = true;
}
if ( mReadExtentFromXml )
{
const QDomNode extentNode = layer_node.namedItem( QStringLiteral( "extent" ) );
if ( !extentNode.isNull() )
{
mXmlExtent2D = QgsXmlUtils::readRectangle( extentNode.toElement() );
}
const QDomNode extent3DNode = layer_node.namedItem( QStringLiteral( "extent3D" ) );
if ( !extent3DNode.isNull() )
{
mXmlExtent3D = QgsXmlUtils::readBox3D( extent3DNode.toElement() );
}
}
// auxiliary layer
const QDomNode asNode = layer_node.namedItem( QStringLiteral( "auxiliaryLayer" ) );
const QDomElement asElem = asNode.toElement();
if ( !asElem.isNull() )
{
mAuxiliaryLayerKey = asElem.attribute( QStringLiteral( "key" ) );
}
// QGIS Server WMS Dimensions
mServerProperties->readXml( layer_node );
return isValid(); // should be true if read successfully
} // void QgsVectorLayer::readXml
void QgsVectorLayer::setDataSourcePrivate( const QString &dataSource, const QString &baseName, const QString &provider,
const QgsDataProvider::ProviderOptions &options, Qgis::DataProviderReadFlags flags )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
Qgis::GeometryType geomType = geometryType();
mDataSource = dataSource;
setName( baseName );
setDataProvider( provider, options, flags );
if ( !isValid() )
{
return;
}
// Always set crs
setCoordinateSystem();
bool loadDefaultStyleFlag = false;
if ( flags & Qgis::DataProviderReadFlag::LoadDefaultStyle )
{
loadDefaultStyleFlag = true;
}
// reset style if loading default style, style is missing, or geometry type is has changed (and layer is valid)
if ( !renderer() || !legend() || ( isValid() && geomType != geometryType() ) || loadDefaultStyleFlag )
{
std::unique_ptr< QgsScopedRuntimeProfile > profile;
if ( QgsApplication::profiler()->groupIsActive( QStringLiteral( "projectload" ) ) )
profile = std::make_unique< QgsScopedRuntimeProfile >( tr( "Load layer style" ), QStringLiteral( "projectload" ) );
bool defaultLoadedFlag = false;
// defer style changed signal until we've set the renderer, labeling, everything.
// we don't want multiple signals!
ScopedIntIncrementor styleChangedSignalBlocker( &mBlockStyleChangedSignal );
// need to check whether the default style included a legend, and if not, we need to make a default legend
// later...
mSetLegendFromStyle = false;
// first check if there is a default style / propertysheet defined
// for this layer and if so apply it
// this should take precedence over all
if ( !defaultLoadedFlag && loadDefaultStyleFlag )
{
loadDefaultStyle( defaultLoadedFlag );
}
if ( loadDefaultStyleFlag && !defaultLoadedFlag && isSpatial() && mDataProvider->capabilities() & Qgis::VectorProviderCapability::CreateRenderer )
{
// if we didn't load a default style for this layer, try to create a renderer directly from the data provider
std::unique_ptr< QgsFeatureRenderer > defaultRenderer( mDataProvider->createRenderer() );
if ( defaultRenderer )
{
defaultLoadedFlag = true;
setRenderer( defaultRenderer.release() );
}
}
// if the default style failed to load or was disabled use some very basic defaults
if ( !defaultLoadedFlag )
{
// add single symbol renderer for spatial layers
setRenderer( isSpatial() ? QgsFeatureRenderer::defaultRenderer( geometryType() ) : nullptr );
}
if ( !mSetLegendFromStyle )
setLegend( QgsMapLayerLegend::defaultVectorLegend( this ) );
if ( mDataProvider->capabilities() & Qgis::VectorProviderCapability::CreateLabeling )
{
std::unique_ptr< QgsAbstractVectorLayerLabeling > defaultLabeling( mDataProvider->createLabeling() );
if ( defaultLabeling )
{
setLabeling( defaultLabeling.release() );
setLabelsEnabled( true );
}
}
styleChangedSignalBlocker.release();
emitStyleChanged();
}
}
QString QgsVectorLayer::loadDefaultStyle( bool &resultFlag )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
// first try to load a user-defined default style - this should always take precedence
QString styleXml = QgsMapLayer::loadDefaultStyle( resultFlag );
if ( resultFlag )
{
// Try to load all stored styles from DB
if ( mLoadAllStoredStyle && mDataProvider && mDataProvider->styleStorageCapabilities().testFlag( Qgis::ProviderStyleStorageCapability::LoadFromDatabase ) )
{
QStringList ids, names, descriptions;
QString errorMessage;
// Get the number of styles related to current layer.
const int relatedStylesCount { listStylesInDatabase( ids, names, descriptions, errorMessage ) };
Q_ASSERT( ids.count() == names.count() );
const QString currentStyleName { mStyleManager->currentStyle() };
for ( int i = 0; i < relatedStylesCount; ++i )
{
if ( names.at( i ) == currentStyleName )
{
continue;
}
errorMessage.clear();
const QString styleXml { getStyleFromDatabase( ids.at( i ), errorMessage ) };
if ( ! styleXml.isEmpty() && errorMessage.isEmpty() )
{
mStyleManager->addStyle( names.at( i ), QgsMapLayerStyle( styleXml ) );
}
else
{
QgsDebugMsgLevel( QStringLiteral( "Error retrieving style %1 from DB: %2" ).arg( ids.at( i ), errorMessage ), 2 );
}
}
}
return styleXml ;
}
if ( isSpatial() && mDataProvider->capabilities() & Qgis::VectorProviderCapability::CreateRenderer )
{
// otherwise try to create a renderer directly from the data provider
std::unique_ptr< QgsFeatureRenderer > defaultRenderer( mDataProvider->createRenderer() );
if ( defaultRenderer )
{
resultFlag = true;
setRenderer( defaultRenderer.release() );
return QString();
}
}
return QString();
}
bool QgsVectorLayer::setDataProvider( QString const &provider, const QgsDataProvider::ProviderOptions &options, Qgis::DataProviderReadFlags flags )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
mProviderKey = provider;
delete mDataProvider;
// For Postgres provider primary key unicity is tested at construction time,
// so it has to be set before initializing the provider,
// this manipulation is necessary to preserve default behavior when
// "trust layer metadata" project level option is set and checkPrimaryKeyUnicity
// was not explicitly passed in the uri
if ( provider.compare( QLatin1String( "postgres" ) ) == 0 )
{
const QString checkUnicityKey { QStringLiteral( "checkPrimaryKeyUnicity" ) };
QgsDataSourceUri uri( mDataSource );
if ( ! uri.hasParam( checkUnicityKey ) )
{
uri.setParam( checkUnicityKey, mReadExtentFromXml ? "0" : "1" );
mDataSource = uri.uri( false );
}
}
std::unique_ptr< QgsScopedRuntimeProfile > profile;
if ( QgsApplication::profiler()->groupIsActive( QStringLiteral( "projectload" ) ) )
profile = std::make_unique< QgsScopedRuntimeProfile >( tr( "Create %1 provider" ).arg( provider ), QStringLiteral( "projectload" ) );
if ( mPreloadedProvider )
mDataProvider = qobject_cast< QgsVectorDataProvider * >( mPreloadedProvider.release() );
else
mDataProvider = qobject_cast<QgsVectorDataProvider *>( QgsProviderRegistry::instance()->createProvider( provider, mDataSource, options, flags ) );
if ( !mDataProvider )
{
setValid( false );
QgsDebugMsgLevel( QStringLiteral( "Unable to get data provider" ), 2 );
return false;
}
mDataProvider->setParent( this );
connect( mDataProvider, &QgsVectorDataProvider::raiseError, this, &QgsVectorLayer::raiseError );
QgsDebugMsgLevel( QStringLiteral( "Instantiated the data provider plugin" ), 2 );
setValid( mDataProvider->isValid() );
if ( !isValid() )
{
QgsDebugMsgLevel( QStringLiteral( "Invalid provider plugin %1" ).arg( QString( mDataSource.toUtf8() ) ), 2 );
return false;
}
if ( profile )
profile->switchTask( tr( "Read layer metadata" ) );
if ( mDataProvider->capabilities() & Qgis::VectorProviderCapability::ReadLayerMetadata )
{
// we combine the provider metadata with the layer's existing metadata, so as not to reset any user customizations to the metadata
// back to the default if a layer's data source is changed
QgsLayerMetadata newMetadata = mDataProvider->layerMetadata();
// this overwrites the provider metadata with any properties which are non-empty from the existing layer metadata
newMetadata.combine( &mMetadata );
setMetadata( newMetadata );
QgsDebugMsgLevel( QStringLiteral( "Set Data provider QgsLayerMetadata identifier[%1]" ).arg( metadata().identifier() ), 4 );
}
// TODO: Check if the provider has the capability to send fullExtentCalculated
connect( mDataProvider, &QgsVectorDataProvider::fullExtentCalculated, this, [this] { updateExtents(); } );
// get and store the feature type
mWkbType = mDataProvider->wkbType();
// before we update the layer fields from the provider, we first copy any default set alias and
// editor widget config from the data provider fields, if present
const QgsFields providerFields = mDataProvider->fields();
for ( const QgsField &field : providerFields )
{
// we only copy defaults from the provider if we aren't overriding any configuration made in the layer
if ( !field.editorWidgetSetup().isNull() && mFieldWidgetSetups.value( field.name() ).isNull() )
{
mFieldWidgetSetups[ field.name() ] = field.editorWidgetSetup();
}
if ( !field.alias().isEmpty() && mAttributeAliasMap.value( field.name() ).isEmpty() )
{
mAttributeAliasMap[ field.name() ] = field.alias();
}
if ( !mAttributeSplitPolicy.contains( field.name() ) )
{
mAttributeSplitPolicy[ field.name() ] = field.splitPolicy();
}
if ( !mAttributeDuplicatePolicy.contains( field.name() ) )
{
mAttributeDuplicatePolicy[ field.name() ] = field.duplicatePolicy();
}
if ( !mAttributeMergePolicy.contains( field.name() ) )
{
mAttributeMergePolicy[ field.name() ] = field.mergePolicy();
}
}
if ( profile )
profile->switchTask( tr( "Read layer fields" ) );
updateFields();
if ( mProviderKey == QLatin1String( "postgres" ) )
{
// update datasource from data provider computed one
mDataSource = mDataProvider->dataSourceUri( false );
QgsDebugMsgLevel( QStringLiteral( "Beautifying layer name %1" ).arg( name() ), 3 );
// adjust the display name for postgres layers
const thread_local QRegularExpression reg( R"lit("[^"]+"\."([^"] + )"( \([^)]+\))?)lit" );
const QRegularExpressionMatch match = reg.match( name() );
if ( match.hasMatch() )
{
QStringList stuff = match.capturedTexts();
QString lName = stuff[1];
const QMap<QString, QgsMapLayer *> &layers = QgsProject::instance()->mapLayers(); // skip-keyword-check
QMap<QString, QgsMapLayer *>::const_iterator it;
for ( it = layers.constBegin(); it != layers.constEnd() && ( *it )->name() != lName; ++it )
;
if ( it != layers.constEnd() && stuff.size() > 2 )
{
lName += '.' + stuff[2].mid( 2, stuff[2].length() - 3 );
}
if ( !lName.isEmpty() )
setName( lName );
}
QgsDebugMsgLevel( QStringLiteral( "Beautified layer name %1" ).arg( name() ), 3 );
}
else if ( mProviderKey == QLatin1String( "osm" ) )
{
// make sure that the "observer" has been removed from URI to avoid crashes
mDataSource = mDataProvider->dataSourceUri();
}
else if ( provider == QLatin1String( "ogr" ) )
{
// make sure that the /vsigzip or /vsizip is added to uri, if applicable
mDataSource = mDataProvider->dataSourceUri();
if ( mDataSource.right( 10 ) == QLatin1String( "|layerid=0" ) )
mDataSource.chop( 10 );
}
else if ( provider == QLatin1String( "memory" ) )
{
// required so that source differs between memory layers
mDataSource = mDataSource + QStringLiteral( "&uid=%1" ).arg( QUuid::createUuid().toString() );
}
else if ( provider == QLatin1String( "hana" ) )
{
// update datasource from data provider computed one
mDataSource = mDataProvider->dataSourceUri( false );
}
connect( mDataProvider, &QgsVectorDataProvider::dataChanged, this, &QgsVectorLayer::emitDataChanged );
connect( mDataProvider, &QgsVectorDataProvider::dataChanged, this, &QgsVectorLayer::removeSelection );
return true;
} // QgsVectorLayer:: setDataProvider
/* virtual */
bool QgsVectorLayer::writeXml( QDomNode &layer_node,
QDomDocument &document,
const QgsReadWriteContext &context ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
// first get the layer element so that we can append the type attribute
QDomElement mapLayerNode = layer_node.toElement();
if ( mapLayerNode.isNull() || ( "maplayer" != mapLayerNode.nodeName() ) )
{
QgsDebugMsgLevel( QStringLiteral( "can't find <maplayer>" ), 2 );
return false;
}
mapLayerNode.setAttribute( QStringLiteral( "type" ), QgsMapLayerFactory::typeToString( Qgis::LayerType::Vector ) );
// set the geometry type
mapLayerNode.setAttribute( QStringLiteral( "geometry" ), QgsWkbTypes::geometryDisplayString( geometryType() ) );
mapLayerNode.setAttribute( QStringLiteral( "wkbType" ), qgsEnumValueToKey( wkbType() ) );
// add provider node
if ( mDataProvider )
{
QDomElement provider = document.createElement( QStringLiteral( "provider" ) );
provider.setAttribute( QStringLiteral( "encoding" ), mDataProvider->encoding() );
QDomText providerText = document.createTextNode( providerType() );
provider.appendChild( providerText );
layer_node.appendChild( provider );
}
//save joins
mJoinBuffer->writeXml( layer_node, document );
// dependencies
QDomElement dependenciesElement = document.createElement( QStringLiteral( "layerDependencies" ) );
const auto constDependencies = dependencies();
for ( const QgsMapLayerDependency &dep : constDependencies )
{
if ( dep.type() != QgsMapLayerDependency::PresenceDependency )
continue;
QDomElement depElem = document.createElement( QStringLiteral( "layer" ) );
depElem.setAttribute( QStringLiteral( "id" ), dep.layerId() );
dependenciesElement.appendChild( depElem );
}
layer_node.appendChild( dependenciesElement );
// change dependencies
QDomElement dataDependenciesElement = document.createElement( QStringLiteral( "dataDependencies" ) );
for ( const QgsMapLayerDependency &dep : constDependencies )
{
if ( dep.type() != QgsMapLayerDependency::DataDependency )
continue;
QDomElement depElem = document.createElement( QStringLiteral( "layer" ) );
depElem.setAttribute( QStringLiteral( "id" ), dep.layerId() );
dataDependenciesElement.appendChild( depElem );
}
layer_node.appendChild( dataDependenciesElement );
// save expression fields
mExpressionFieldBuffer->writeXml( layer_node, document );
writeStyleManager( layer_node, document );
// auxiliary layer
QDomElement asElem = document.createElement( QStringLiteral( "auxiliaryLayer" ) );
if ( mAuxiliaryLayer )
{
const QString pkField = mAuxiliaryLayer->joinInfo().targetFieldName();
asElem.setAttribute( QStringLiteral( "key" ), pkField );
}
layer_node.appendChild( asElem );
// renderer specific settings
QString errorMsg;
return writeSymbology( layer_node, document, errorMsg, context );
}
QString QgsVectorLayer::encodedSource( const QString &source, const QgsReadWriteContext &context ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( providerType() == QLatin1String( "memory" ) )
{
// Refetch the source from the provider, because adding fields actually changes the source for this provider.
return dataProvider()->dataSourceUri();
}
return QgsProviderRegistry::instance()->absoluteToRelativeUri( mProviderKey, source, context );
}
QString QgsVectorLayer::decodedSource( const QString &source, const QString &provider, const QgsReadWriteContext &context ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return QgsProviderRegistry::instance()->relativeToAbsoluteUri( provider, source, context );
}
void QgsVectorLayer::resolveReferences( QgsProject *project )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QgsMapLayer::resolveReferences( project );
mJoinBuffer->resolveReferences( project );
}
bool QgsVectorLayer::readSymbology( const QDomNode &layerNode, QString &errorMessage,
QgsReadWriteContext &context, QgsMapLayer::StyleCategories categories )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( categories.testFlag( Fields ) )
{
if ( !mExpressionFieldBuffer )
mExpressionFieldBuffer = new QgsExpressionFieldBuffer();
mExpressionFieldBuffer->readXml( layerNode );
updateFields();
}
if ( categories.testFlag( Relations ) )
{
QgsReadWriteContextCategoryPopper p = context.enterCategory( tr( "Relations" ) );
// Restore referenced layers: relations where "this" is the child layer (the referencing part, that holds the FK)
QDomNodeList referencedLayersNodeList = layerNode.toElement().elementsByTagName( QStringLiteral( "referencedLayers" ) );
if ( referencedLayersNodeList.size() > 0 )
{
const QDomNodeList relationNodes { referencedLayersNodeList.at( 0 ).childNodes() };
for ( int i = 0; i < relationNodes.length(); ++i )
{
const QDomElement relationElement = relationNodes.at( i ).toElement();
mWeakRelations.push_back( QgsWeakRelation::readXml( this, QgsWeakRelation::Referencing, relationElement, context.pathResolver() ) );
}
}
// Restore referencing layers: relations where "this" is the parent layer (the referenced part where the FK points to)
QDomNodeList referencingLayersNodeList = layerNode.toElement().elementsByTagName( QStringLiteral( "referencingLayers" ) );
if ( referencingLayersNodeList.size() > 0 )
{
const QDomNodeList relationNodes { referencingLayersNodeList.at( 0 ).childNodes() };
for ( int i = 0; i < relationNodes.length(); ++i )
{
const QDomElement relationElement = relationNodes.at( i ).toElement();
mWeakRelations.push_back( QgsWeakRelation::readXml( this, QgsWeakRelation::Referenced, relationElement, context.pathResolver() ) );
}
}
}
QDomElement layerElement = layerNode.toElement();
readCommonStyle( layerElement, context, categories );
readStyle( layerNode, errorMessage, context, categories );
if ( categories.testFlag( MapTips ) )
{
QDomElement mapTipElem = layerNode.namedItem( QStringLiteral( "mapTip" ) ).toElement();
setMapTipTemplate( mapTipElem.text() );
setMapTipsEnabled( mapTipElem.attribute( QStringLiteral( "enabled" ), QStringLiteral( "1" ) ).toInt() == 1 );
}
if ( categories.testFlag( LayerConfiguration ) )
mDisplayExpression = layerNode.namedItem( QStringLiteral( "previewExpression" ) ).toElement().text();
// Try to migrate pre QGIS 3.0 display field property
QString displayField = layerNode.namedItem( QStringLiteral( "displayfield" ) ).toElement().text();
if ( mFields.lookupField( displayField ) < 0 )
{
// if it's not a field, it's a maptip
if ( mMapTipTemplate.isEmpty() && categories.testFlag( MapTips ) )
mMapTipTemplate = displayField;
}
else
{
if ( mDisplayExpression.isEmpty() && categories.testFlag( LayerConfiguration ) )
mDisplayExpression = QgsExpression::quotedColumnRef( displayField );
}
// process the attribute actions
if ( categories.testFlag( Actions ) )
mActions->readXml( layerNode );
if ( categories.testFlag( Fields ) )
{
// IMPORTANT - we don't clear mAttributeAliasMap here, as it may contain aliases which are coming direct
// from the data provider. Instead we leave any existing aliases and only overwrite them if the style
// has a specific value for that field's alias
QDomNode aliasesNode = layerNode.namedItem( QStringLiteral( "aliases" ) );
if ( !aliasesNode.isNull() )
{
QDomElement aliasElem;
QDomNodeList aliasNodeList = aliasesNode.toElement().elementsByTagName( QStringLiteral( "alias" ) );
for ( int i = 0; i < aliasNodeList.size(); ++i )
{
aliasElem = aliasNodeList.at( i ).toElement();
QString field;
if ( aliasElem.hasAttribute( QStringLiteral( "field" ) ) )
{
field = aliasElem.attribute( QStringLiteral( "field" ) );
}
else
{
int index = aliasElem.attribute( QStringLiteral( "index" ) ).toInt();
if ( index >= 0 && index < fields().count() )
field = fields().at( index ).name();
}
QString alias;
if ( !aliasElem.attribute( QStringLiteral( "name" ) ).isEmpty() )
{
//if it has alias
alias = context.projectTranslator()->translate( QStringLiteral( "project:layers:%1:fieldaliases" ).arg( layerNode.namedItem( QStringLiteral( "id" ) ).toElement().text() ), aliasElem.attribute( QStringLiteral( "name" ) ) );
QgsDebugMsgLevel( "context" + QStringLiteral( "project:layers:%1:fieldaliases" ).arg( layerNode.namedItem( QStringLiteral( "id" ) ).toElement().text() ) + " source " + aliasElem.attribute( QStringLiteral( "name" ) ), 3 );
}
else
{
//if it has no alias, it should be the fields translation
alias = context.projectTranslator()->translate( QStringLiteral( "project:layers:%1:fieldaliases" ).arg( layerNode.namedItem( QStringLiteral( "id" ) ).toElement().text() ), field );
QgsDebugMsgLevel( "context" + QStringLiteral( "project:layers:%1:fieldaliases" ).arg( layerNode.namedItem( QStringLiteral( "id" ) ).toElement().text() ) + " source " + field, 3 );
//if it gets the exact field value, there has been no translation (or not even translation loaded) - so no alias should be generated;
if ( alias == aliasElem.attribute( QStringLiteral( "field" ) ) )
alias.clear();
}
QgsDebugMsgLevel( "field " + field + " origalias " + aliasElem.attribute( QStringLiteral( "name" ) ) + " trans " + alias, 3 );
mAttributeAliasMap.insert( field, alias );
}
}
// IMPORTANT - we don't clear mAttributeSplitPolicy here, as it may contain policies which are coming direct
// from the data provider. Instead we leave any existing policies and only overwrite them if the style
// has a specific value for that field's policy
const QDomNode splitPoliciesNode = layerNode.namedItem( QStringLiteral( "splitPolicies" ) );
if ( !splitPoliciesNode.isNull() )
{
const QDomNodeList splitPolicyNodeList = splitPoliciesNode.toElement().elementsByTagName( QStringLiteral( "policy" ) );
for ( int i = 0; i < splitPolicyNodeList.size(); ++i )
{
const QDomElement splitPolicyElem = splitPolicyNodeList.at( i ).toElement();
const QString field = splitPolicyElem.attribute( QStringLiteral( "field" ) );
const Qgis::FieldDomainSplitPolicy policy = qgsEnumKeyToValue( splitPolicyElem.attribute( QStringLiteral( "policy" ) ), Qgis::FieldDomainSplitPolicy::Duplicate );
mAttributeSplitPolicy.insert( field, policy );
}
}
// The duplicate policy is - unlike alias and split policy - never defined by the data provider, so we clear the map
mAttributeDuplicatePolicy.clear();
const QDomNode duplicatePoliciesNode = layerNode.namedItem( QStringLiteral( "duplicatePolicies" ) );
if ( !duplicatePoliciesNode.isNull() )
{
const QDomNodeList duplicatePolicyNodeList = duplicatePoliciesNode.toElement().elementsByTagName( QStringLiteral( "policy" ) );
for ( int i = 0; i < duplicatePolicyNodeList.size(); ++i )
{
const QDomElement duplicatePolicyElem = duplicatePolicyNodeList.at( i ).toElement();
const QString field = duplicatePolicyElem.attribute( QStringLiteral( "field" ) );
const Qgis::FieldDuplicatePolicy policy = qgsEnumKeyToValue( duplicatePolicyElem.attribute( QStringLiteral( "policy" ) ), Qgis::FieldDuplicatePolicy::Duplicate );
mAttributeDuplicatePolicy.insert( field, policy );
}
}
const QDomNode mergePoliciesNode = layerNode.namedItem( QStringLiteral( "mergePolicies" ) );
if ( !mergePoliciesNode.isNull() )
{
const QDomNodeList mergePolicyNodeList = mergePoliciesNode.toElement().elementsByTagName( QStringLiteral( "policy" ) );
for ( int i = 0; i < mergePolicyNodeList.size(); ++i )
{
const QDomElement mergePolicyElem = mergePolicyNodeList.at( i ).toElement();
const QString field = mergePolicyElem.attribute( QStringLiteral( "field" ) );
const Qgis::FieldDomainMergePolicy policy = qgsEnumKeyToValue( mergePolicyElem.attribute( QStringLiteral( "policy" ) ), Qgis::FieldDomainMergePolicy::UnsetField );
mAttributeMergePolicy.insert( field, policy );
}
}
// default expressions
mDefaultExpressionMap.clear();
QDomNode defaultsNode = layerNode.namedItem( QStringLiteral( "defaults" ) );
if ( !defaultsNode.isNull() )
{
QDomNodeList defaultNodeList = defaultsNode.toElement().elementsByTagName( QStringLiteral( "default" ) );
for ( int i = 0; i < defaultNodeList.size(); ++i )
{
QDomElement defaultElem = defaultNodeList.at( i ).toElement();
QString field = defaultElem.attribute( QStringLiteral( "field" ), QString() );
QString expression = defaultElem.attribute( QStringLiteral( "expression" ), QString() );
bool applyOnUpdate = defaultElem.attribute( QStringLiteral( "applyOnUpdate" ), QStringLiteral( "0" ) ) == QLatin1String( "1" );
if ( field.isEmpty() || expression.isEmpty() )
continue;
mDefaultExpressionMap.insert( field, QgsDefaultValue( expression, applyOnUpdate ) );
}
}
// constraints
mFieldConstraints.clear();
mFieldConstraintStrength.clear();
QDomNode constraintsNode = layerNode.namedItem( QStringLiteral( "constraints" ) );
if ( !constraintsNode.isNull() )
{
QDomNodeList constraintNodeList = constraintsNode.toElement().elementsByTagName( QStringLiteral( "constraint" ) );
for ( int i = 0; i < constraintNodeList.size(); ++i )
{
QDomElement constraintElem = constraintNodeList.at( i ).toElement();
QString field = constraintElem.attribute( QStringLiteral( "field" ), QString() );
int constraints = constraintElem.attribute( QStringLiteral( "constraints" ), QStringLiteral( "0" ) ).toInt();
if ( field.isEmpty() || constraints == 0 )
continue;
mFieldConstraints.insert( field, static_cast< QgsFieldConstraints::Constraints >( constraints ) );
int uniqueStrength = constraintElem.attribute( QStringLiteral( "unique_strength" ), QStringLiteral( "1" ) ).toInt();
int notNullStrength = constraintElem.attribute( QStringLiteral( "notnull_strength" ), QStringLiteral( "1" ) ).toInt();
int expStrength = constraintElem.attribute( QStringLiteral( "exp_strength" ), QStringLiteral( "1" ) ).toInt();
mFieldConstraintStrength.insert( qMakePair( field, QgsFieldConstraints::ConstraintUnique ), static_cast< QgsFieldConstraints::ConstraintStrength >( uniqueStrength ) );
mFieldConstraintStrength.insert( qMakePair( field, QgsFieldConstraints::ConstraintNotNull ), static_cast< QgsFieldConstraints::ConstraintStrength >( notNullStrength ) );
mFieldConstraintStrength.insert( qMakePair( field, QgsFieldConstraints::ConstraintExpression ), static_cast< QgsFieldConstraints::ConstraintStrength >( expStrength ) );
}
}
mFieldConstraintExpressions.clear();
QDomNode constraintExpressionsNode = layerNode.namedItem( QStringLiteral( "constraintExpressions" ) );
if ( !constraintExpressionsNode.isNull() )
{
QDomNodeList constraintNodeList = constraintExpressionsNode.toElement().elementsByTagName( QStringLiteral( "constraint" ) );
for ( int i = 0; i < constraintNodeList.size(); ++i )
{
QDomElement constraintElem = constraintNodeList.at( i ).toElement();
QString field = constraintElem.attribute( QStringLiteral( "field" ), QString() );
QString exp = constraintElem.attribute( QStringLiteral( "exp" ), QString() );
QString desc = constraintElem.attribute( QStringLiteral( "desc" ), QString() );
if ( field.isEmpty() || exp.isEmpty() )
continue;
mFieldConstraintExpressions.insert( field, qMakePair( exp, desc ) );
}
}
updateFields();
}
// load field configuration
if ( categories.testFlag( Fields ) || categories.testFlag( Forms ) )
{
QgsReadWriteContextCategoryPopper p = context.enterCategory( tr( "Forms" ) );
QDomElement widgetsElem = layerNode.namedItem( QStringLiteral( "fieldConfiguration" ) ).toElement();
QDomNodeList fieldConfigurationElementList = widgetsElem.elementsByTagName( QStringLiteral( "field" ) );
for ( int i = 0; i < fieldConfigurationElementList.size(); ++i )
{
const QDomElement fieldConfigElement = fieldConfigurationElementList.at( i ).toElement();
const QDomElement fieldWidgetElement = fieldConfigElement.elementsByTagName( QStringLiteral( "editWidget" ) ).at( 0 ).toElement();
QString fieldName = fieldConfigElement.attribute( QStringLiteral( "name" ) );
if ( categories.testFlag( Fields ) )
mFieldConfigurationFlags[fieldName] = qgsFlagKeysToValue( fieldConfigElement.attribute( QStringLiteral( "configurationFlags" ) ), Qgis::FieldConfigurationFlag::NoFlag );
// load editor widget configuration
if ( categories.testFlag( Forms ) )
{
const QString widgetType = fieldWidgetElement.attribute( QStringLiteral( "type" ) );
const QDomElement cfgElem = fieldConfigElement.elementsByTagName( QStringLiteral( "config" ) ).at( 0 ).toElement();
const QDomElement optionsElem = cfgElem.childNodes().at( 0 ).toElement();
QVariantMap optionsMap = QgsXmlUtils::readVariant( optionsElem ).toMap();
// translate widget configuration strings
if ( widgetType == QStringLiteral( "ValueRelation" ) )
{
optionsMap[ QStringLiteral( "Value" ) ] = context.projectTranslator()->translate( QStringLiteral( "project:layers:%1:fields:%2:valuerelationvalue" ).arg( layerNode.namedItem( QStringLiteral( "id" ) ).toElement().text(), fieldName ), optionsMap[ QStringLiteral( "Value" ) ].toString() );
}
if ( widgetType == QStringLiteral( "ValueMap" ) )
{
if ( optionsMap[ QStringLiteral( "map" ) ].canConvert<QList<QVariant>>() )
{
QList<QVariant> translatedValueList;
const QList<QVariant> valueList = optionsMap[ QStringLiteral( "map" )].toList();
for ( int i = 0, row = 0; i < valueList.count(); i++, row++ )
{
QMap<QString, QVariant> translatedValueMap;
QString translatedKey = context.projectTranslator()->translate( QStringLiteral( "project:layers:%1:fields:%2:valuemapdescriptions" ).arg( layerNode.namedItem( QStringLiteral( "id" ) ).toElement().text(), fieldName ), valueList[i].toMap().constBegin().key() );
translatedValueMap.insert( translatedKey, valueList[i].toMap().constBegin().value() );
translatedValueList.append( translatedValueMap );
}
optionsMap.insert( QStringLiteral( "map" ), translatedValueList );
}
}
QgsEditorWidgetSetup setup = QgsEditorWidgetSetup( widgetType, optionsMap );
mFieldWidgetSetups[fieldName] = setup;
}
}
}
// Legacy reading for QGIS 3.14 and older projects
// Attributes excluded from WMS and WFS
if ( categories.testFlag( Fields ) )
{
const QList<QPair<QString, Qgis::FieldConfigurationFlag>> legacyConfig
{
qMakePair( QStringLiteral( "excludeAttributesWMS" ), Qgis::FieldConfigurationFlag::HideFromWms ),
qMakePair( QStringLiteral( "excludeAttributesWFS" ), Qgis::FieldConfigurationFlag::HideFromWfs )
};
for ( const auto &config : legacyConfig )
{
QDomNode excludeNode = layerNode.namedItem( config.first );
if ( !excludeNode.isNull() )
{
QDomNodeList attributeNodeList = excludeNode.toElement().elementsByTagName( QStringLiteral( "attribute" ) );
for ( int i = 0; i < attributeNodeList.size(); ++i )
{
QString fieldName = attributeNodeList.at( i ).toElement().text();
if ( !mFieldConfigurationFlags.contains( fieldName ) )
mFieldConfigurationFlags[fieldName] = config.second;
else
mFieldConfigurationFlags[fieldName].setFlag( config.second, true );
}
}
}
}
if ( categories.testFlag( GeometryOptions ) )
mGeometryOptions->readXml( layerNode.namedItem( QStringLiteral( "geometryOptions" ) ) );
if ( categories.testFlag( Forms ) )
mEditFormConfig.readXml( layerNode, context );
if ( categories.testFlag( AttributeTable ) )
{
mAttributeTableConfig.readXml( layerNode );
mConditionalStyles->readXml( layerNode, context );
mStoredExpressionManager->readXml( layerNode );
}
if ( categories.testFlag( CustomProperties ) )
readCustomProperties( layerNode, QStringLiteral( "variable" ) );
QDomElement mapLayerNode = layerNode.toElement();
if ( categories.testFlag( LayerConfiguration )
&& mapLayerNode.attribute( QStringLiteral( "readOnly" ), QStringLiteral( "0" ) ).toInt() == 1 )
mReadOnly = true;
updateFields();
if ( categories.testFlag( Legend ) )
{
QgsReadWriteContextCategoryPopper p = context.enterCategory( tr( "Legend" ) );
const QDomElement legendElem = layerNode.firstChildElement( QStringLiteral( "legend" ) );
if ( !legendElem.isNull() )
{
std::unique_ptr< QgsMapLayerLegend > legend( QgsMapLayerLegend::defaultVectorLegend( this ) );
legend->readXml( legendElem, context );
setLegend( legend.release() );
mSetLegendFromStyle = true;
}
}
return true;
}
bool QgsVectorLayer::readStyle( const QDomNode &node, QString &errorMessage,
QgsReadWriteContext &context, QgsMapLayer::StyleCategories categories )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
bool result = true;
emit readCustomSymbology( node.toElement(), errorMessage );
// we must try to restore a renderer if our geometry type is unknown
// as this allows the renderer to be correctly restored even for layers
// with broken sources
if ( isSpatial() || mWkbType == Qgis::WkbType::Unknown )
{
// defer style changed signal until we've set the renderer, labeling, everything.
// we don't want multiple signals!
ScopedIntIncrementor styleChangedSignalBlocker( &mBlockStyleChangedSignal );
// try renderer v2 first
if ( categories.testFlag( Symbology ) )
{
QgsReadWriteContextCategoryPopper p = context.enterCategory( tr( "Symbology" ) );
QDomElement rendererElement = node.firstChildElement( RENDERER_TAG_NAME );
if ( !rendererElement.isNull() )
{
QgsFeatureRenderer *r = QgsFeatureRenderer::load( rendererElement, context );
if ( r )
{
setRenderer( r );
}
else
{
result = false;
}
}
// make sure layer has a renderer - if none exists, fallback to a default renderer
if ( isSpatial() && !renderer() )
{
setRenderer( QgsFeatureRenderer::defaultRenderer( geometryType() ) );
}
if ( mSelectionProperties )
mSelectionProperties->readXml( node.toElement(), context );
}
// read labeling definition
if ( categories.testFlag( Labeling ) )
{
QgsReadWriteContextCategoryPopper p = context.enterCategory( tr( "Labeling" ) );
QDomElement labelingElement = node.firstChildElement( QStringLiteral( "labeling" ) );
QgsAbstractVectorLayerLabeling *labeling = nullptr;
if ( labelingElement.isNull() ||
( labelingElement.attribute( QStringLiteral( "type" ) ) == QLatin1String( "simple" ) && labelingElement.firstChildElement( QStringLiteral( "settings" ) ).isNull() ) )
{
// make sure we have custom properties for labeling for 2.x projects
// (custom properties should be already loaded when reading the whole layer from XML,
// but when reading style, custom properties are not read)
readCustomProperties( node, QStringLiteral( "labeling" ) );
// support for pre-QGIS 3 labeling configurations written in custom properties
labeling = readLabelingFromCustomProperties();
}
else
{
labeling = QgsAbstractVectorLayerLabeling::create( labelingElement, context );
}
setLabeling( labeling );
if ( node.toElement().hasAttribute( QStringLiteral( "labelsEnabled" ) ) )
mLabelsEnabled = node.toElement().attribute( QStringLiteral( "labelsEnabled" ) ).toInt();
else
mLabelsEnabled = true;
}
if ( categories.testFlag( Symbology ) )
{
// get and set the blend mode if it exists
QDomNode blendModeNode = node.namedItem( QStringLiteral( "blendMode" ) );
if ( !blendModeNode.isNull() )
{
QDomElement e = blendModeNode.toElement();
setBlendMode( QgsPainting::getCompositionMode( static_cast< Qgis::BlendMode >( e.text().toInt() ) ) );
}
// get and set the feature blend mode if it exists
QDomNode featureBlendModeNode = node.namedItem( QStringLiteral( "featureBlendMode" ) );
if ( !featureBlendModeNode.isNull() )
{
QDomElement e = featureBlendModeNode.toElement();
setFeatureBlendMode( QgsPainting::getCompositionMode( static_cast< Qgis::BlendMode >( e.text().toInt() ) ) );
}
}
// get and set the layer transparency and scale visibility if they exists
if ( categories.testFlag( Rendering ) )
{
QDomNode layerTransparencyNode = node.namedItem( QStringLiteral( "layerTransparency" ) );
if ( !layerTransparencyNode.isNull() )
{
QDomElement e = layerTransparencyNode.toElement();
setOpacity( 1.0 - e.text().toInt() / 100.0 );
}
QDomNode layerOpacityNode = node.namedItem( QStringLiteral( "layerOpacity" ) );
if ( !layerOpacityNode.isNull() )
{
QDomElement e = layerOpacityNode.toElement();
setOpacity( e.text().toDouble() );
}
const bool hasScaleBasedVisibiliy { node.attributes().namedItem( QStringLiteral( "hasScaleBasedVisibilityFlag" ) ).nodeValue() == '1' };
setScaleBasedVisibility( hasScaleBasedVisibiliy );
bool ok;
const double maxScale { node.attributes().namedItem( QStringLiteral( "maxScale" ) ).nodeValue().toDouble( &ok ) };
if ( ok )
{
setMaximumScale( maxScale );
}
const double minScale { node.attributes().namedItem( QStringLiteral( "minScale" ) ).nodeValue().toDouble( &ok ) };
if ( ok )
{
setMinimumScale( minScale );
}
QDomElement e = node.toElement();
// get the simplification drawing settings
mSimplifyMethod.setSimplifyHints( static_cast< Qgis::VectorRenderingSimplificationFlags >( e.attribute( QStringLiteral( "simplifyDrawingHints" ), QStringLiteral( "1" ) ).toInt() ) );
mSimplifyMethod.setSimplifyAlgorithm( static_cast< Qgis::VectorSimplificationAlgorithm >( e.attribute( QStringLiteral( "simplifyAlgorithm" ), QStringLiteral( "0" ) ).toInt() ) );
mSimplifyMethod.setThreshold( e.attribute( QStringLiteral( "simplifyDrawingTol" ), QStringLiteral( "1" ) ).toFloat() );
mSimplifyMethod.setForceLocalOptimization( e.attribute( QStringLiteral( "simplifyLocal" ), QStringLiteral( "1" ) ).toInt() );
mSimplifyMethod.setMaximumScale( e.attribute( QStringLiteral( "simplifyMaxScale" ), QStringLiteral( "1" ) ).toFloat() );
if ( mRenderer )
mRenderer->setReferenceScale( e.attribute( QStringLiteral( "symbologyReferenceScale" ), QStringLiteral( "-1" ) ).toDouble() );
}
//diagram renderer and diagram layer settings
if ( categories.testFlag( Diagrams ) )
{
QgsReadWriteContextCategoryPopper p = context.enterCategory( tr( "Diagrams" ) );
delete mDiagramRenderer;
mDiagramRenderer = nullptr;
QDomElement singleCatDiagramElem = node.firstChildElement( QStringLiteral( "SingleCategoryDiagramRenderer" ) );
if ( !singleCatDiagramElem.isNull() )
{
mDiagramRenderer = new QgsSingleCategoryDiagramRenderer();
mDiagramRenderer->readXml( singleCatDiagramElem, context );
}
QDomElement linearDiagramElem = node.firstChildElement( QStringLiteral( "LinearlyInterpolatedDiagramRenderer" ) );
if ( !linearDiagramElem.isNull() )
{
if ( linearDiagramElem.hasAttribute( QStringLiteral( "classificationAttribute" ) ) )
{
// fix project from before QGIS 3.0
int idx = linearDiagramElem.attribute( QStringLiteral( "classificationAttribute" ) ).toInt();
if ( idx >= 0 && idx < mFields.count() )
linearDiagramElem.setAttribute( QStringLiteral( "classificationField" ), mFields.at( idx ).name() );
}
mDiagramRenderer = new QgsLinearlyInterpolatedDiagramRenderer();
mDiagramRenderer->readXml( linearDiagramElem, context );
}
QDomElement stackedDiagramElem = node.firstChildElement( QStringLiteral( "StackedDiagramRenderer" ) );
if ( !stackedDiagramElem.isNull() )
{
mDiagramRenderer = new QgsStackedDiagramRenderer();
mDiagramRenderer->readXml( stackedDiagramElem, context );
}
if ( mDiagramRenderer )
{
QDomElement diagramSettingsElem = node.firstChildElement( QStringLiteral( "DiagramLayerSettings" ) );
if ( !diagramSettingsElem.isNull() )
{
bool oldXPos = diagramSettingsElem.hasAttribute( QStringLiteral( "xPosColumn" ) );
bool oldYPos = diagramSettingsElem.hasAttribute( QStringLiteral( "yPosColumn" ) );
bool oldShow = diagramSettingsElem.hasAttribute( QStringLiteral( "showColumn" ) );
if ( oldXPos || oldYPos || oldShow )
{
// fix project from before QGIS 3.0
QgsPropertyCollection ddp;
if ( oldXPos )
{
int xPosColumn = diagramSettingsElem.attribute( QStringLiteral( "xPosColumn" ) ).toInt();
if ( xPosColumn >= 0 && xPosColumn < mFields.count() )
ddp.setProperty( QgsDiagramLayerSettings::Property::PositionX, QgsProperty::fromField( mFields.at( xPosColumn ).name(), true ) );
}
if ( oldYPos )
{
int yPosColumn = diagramSettingsElem.attribute( QStringLiteral( "yPosColumn" ) ).toInt();
if ( yPosColumn >= 0 && yPosColumn < mFields.count() )
ddp.setProperty( QgsDiagramLayerSettings::Property::PositionY, QgsProperty::fromField( mFields.at( yPosColumn ).name(), true ) );
}
if ( oldShow )
{
int showColumn = diagramSettingsElem.attribute( QStringLiteral( "showColumn" ) ).toInt();
if ( showColumn >= 0 && showColumn < mFields.count() )
ddp.setProperty( QgsDiagramLayerSettings::Property::Show, QgsProperty::fromField( mFields.at( showColumn ).name(), true ) );
}
QDomElement propertiesElem = diagramSettingsElem.ownerDocument().createElement( QStringLiteral( "properties" ) );
QgsPropertiesDefinition defs = QgsPropertiesDefinition
{
{ static_cast< int >( QgsDiagramLayerSettings::Property::PositionX ), QgsPropertyDefinition( "positionX", QObject::tr( "Position (X)" ), QgsPropertyDefinition::Double ) },
{ static_cast< int >( QgsDiagramLayerSettings::Property::PositionY ), QgsPropertyDefinition( "positionY", QObject::tr( "Position (Y)" ), QgsPropertyDefinition::Double ) },
{ static_cast< int >( QgsDiagramLayerSettings::Property::Show ), QgsPropertyDefinition( "show", QObject::tr( "Show diagram" ), QgsPropertyDefinition::Boolean ) },
};
ddp.writeXml( propertiesElem, defs );
diagramSettingsElem.appendChild( propertiesElem );
}
delete mDiagramLayerSettings;
mDiagramLayerSettings = new QgsDiagramLayerSettings();
mDiagramLayerSettings->readXml( diagramSettingsElem );
}
}
}
// end diagram
styleChangedSignalBlocker.release();
emitStyleChanged();
}
return result;
}
bool QgsVectorLayer::writeSymbology( QDomNode &node, QDomDocument &doc, QString &errorMessage,
const QgsReadWriteContext &context, QgsMapLayer::StyleCategories categories ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QDomElement layerElement = node.toElement();
writeCommonStyle( layerElement, doc, context, categories );
( void )writeStyle( node, doc, errorMessage, context, categories );
if ( categories.testFlag( GeometryOptions ) )
mGeometryOptions->writeXml( node );
if ( categories.testFlag( Legend ) && legend() )
{
QDomElement legendElement = legend()->writeXml( doc, context );
if ( !legendElement.isNull() )
node.appendChild( legendElement );
}
// Relation information for both referenced and referencing sides
if ( categories.testFlag( Relations ) )
{
if ( QgsProject *p = project() )
{
// Store referenced layers: relations where "this" is the child layer (the referencing part, that holds the FK)
QDomElement referencedLayersElement = doc.createElement( QStringLiteral( "referencedLayers" ) );
node.appendChild( referencedLayersElement );
const QList<QgsRelation> referencingRelations { p->relationManager()->referencingRelations( this ) };
for ( const QgsRelation &rel : referencingRelations )
{
switch ( rel.type() )
{
case Qgis::RelationshipType::Normal:
QgsWeakRelation::writeXml( this, QgsWeakRelation::Referencing, rel, referencedLayersElement, doc );
break;
case Qgis::RelationshipType::Generated:
break;
}
}
// Store referencing layers: relations where "this" is the parent layer (the referenced part, that holds the FK)
QDomElement referencingLayersElement = doc.createElement( QStringLiteral( "referencingLayers" ) );
node.appendChild( referencingLayersElement );
const QList<QgsRelation> referencedRelations { p->relationManager()->referencedRelations( this ) };
for ( const QgsRelation &rel : referencedRelations )
{
switch ( rel.type() )
{
case Qgis::RelationshipType::Normal:
QgsWeakRelation::writeXml( this, QgsWeakRelation::Referenced, rel, referencingLayersElement, doc );
break;
case Qgis::RelationshipType::Generated:
break;
}
}
}
}
// write field configurations
if ( categories.testFlag( Fields ) || categories.testFlag( Forms ) )
{
QDomElement fieldConfigurationElement;
// field configuration flag
fieldConfigurationElement = doc.createElement( QStringLiteral( "fieldConfiguration" ) );
node.appendChild( fieldConfigurationElement );
for ( const QgsField &field : std::as_const( mFields ) )
{
QDomElement fieldElement = doc.createElement( QStringLiteral( "field" ) );
fieldElement.setAttribute( QStringLiteral( "name" ), field.name() );
fieldConfigurationElement.appendChild( fieldElement );
if ( categories.testFlag( Fields ) )
{
fieldElement.setAttribute( QStringLiteral( "configurationFlags" ), qgsFlagValueToKeys( field.configurationFlags() ) );
}
if ( categories.testFlag( Forms ) )
{
QgsEditorWidgetSetup widgetSetup = field.editorWidgetSetup();
// TODO : wrap this part in an if to only save if it was user-modified
QDomElement editWidgetElement = doc.createElement( QStringLiteral( "editWidget" ) );
fieldElement.appendChild( editWidgetElement );
editWidgetElement.setAttribute( QStringLiteral( "type" ), field.editorWidgetSetup().type() );
QDomElement editWidgetConfigElement = doc.createElement( QStringLiteral( "config" ) );
editWidgetConfigElement.appendChild( QgsXmlUtils::writeVariant( widgetSetup.config(), doc ) );
editWidgetElement.appendChild( editWidgetConfigElement );
// END TODO : wrap this part in an if to only save if it was user-modified
}
}
}
if ( categories.testFlag( Fields ) )
{
//attribute aliases
QDomElement aliasElem = doc.createElement( QStringLiteral( "aliases" ) );
for ( const QgsField &field : std::as_const( mFields ) )
{
QDomElement aliasEntryElem = doc.createElement( QStringLiteral( "alias" ) );
aliasEntryElem.setAttribute( QStringLiteral( "field" ), field.name() );
aliasEntryElem.setAttribute( QStringLiteral( "index" ), mFields.indexFromName( field.name() ) );
aliasEntryElem.setAttribute( QStringLiteral( "name" ), field.alias() );
aliasElem.appendChild( aliasEntryElem );
}
node.appendChild( aliasElem );
//split policies
{
QDomElement splitPoliciesElement = doc.createElement( QStringLiteral( "splitPolicies" ) );
bool hasNonDefaultSplitPolicies = false;
for ( const QgsField &field : std::as_const( mFields ) )
{
if ( field.splitPolicy() != Qgis::FieldDomainSplitPolicy::Duplicate )
{
QDomElement splitPolicyElem = doc.createElement( QStringLiteral( "policy" ) );
splitPolicyElem.setAttribute( QStringLiteral( "field" ), field.name() );
splitPolicyElem.setAttribute( QStringLiteral( "policy" ), qgsEnumValueToKey( field.splitPolicy() ) );
splitPoliciesElement.appendChild( splitPolicyElem );
hasNonDefaultSplitPolicies = true;
}
}
if ( hasNonDefaultSplitPolicies )
node.appendChild( splitPoliciesElement );
}
//duplicate policies
{
QDomElement duplicatePoliciesElement = doc.createElement( QStringLiteral( "duplicatePolicies" ) );
bool hasNonDefaultDuplicatePolicies = false;
for ( const QgsField &field : std::as_const( mFields ) )
{
if ( field.duplicatePolicy() != Qgis::FieldDuplicatePolicy::Duplicate )
{
QDomElement duplicatePolicyElem = doc.createElement( QStringLiteral( "policy" ) );
duplicatePolicyElem.setAttribute( QStringLiteral( "field" ), field.name() );
duplicatePolicyElem.setAttribute( QStringLiteral( "policy" ), qgsEnumValueToKey( field.duplicatePolicy() ) );
duplicatePoliciesElement.appendChild( duplicatePolicyElem );
hasNonDefaultDuplicatePolicies = true;
}
}
if ( hasNonDefaultDuplicatePolicies )
node.appendChild( duplicatePoliciesElement );
}
//merge policies
{
QDomElement mergePoliciesElement = doc.createElement( QStringLiteral( "mergePolicies" ) );
bool hasNonDefaultMergePolicies = false;
for ( const QgsField &field : std::as_const( mFields ) )
{
if ( field.mergePolicy() != Qgis::FieldDomainMergePolicy::UnsetField )
{
QDomElement mergePolicyElem = doc.createElement( QStringLiteral( "policy" ) );
mergePolicyElem.setAttribute( QStringLiteral( "field" ), field.name() );
mergePolicyElem.setAttribute( QStringLiteral( "policy" ), qgsEnumValueToKey( field.mergePolicy() ) );
mergePoliciesElement.appendChild( mergePolicyElem );
hasNonDefaultMergePolicies = true;
}
}
if ( hasNonDefaultMergePolicies )
node.appendChild( mergePoliciesElement );
}
//default expressions
QDomElement defaultsElem = doc.createElement( QStringLiteral( "defaults" ) );
for ( const QgsField &field : std::as_const( mFields ) )
{
QDomElement defaultElem = doc.createElement( QStringLiteral( "default" ) );
defaultElem.setAttribute( QStringLiteral( "field" ), field.name() );
defaultElem.setAttribute( QStringLiteral( "expression" ), field.defaultValueDefinition().expression() );
defaultElem.setAttribute( QStringLiteral( "applyOnUpdate" ), field.defaultValueDefinition().applyOnUpdate() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
defaultsElem.appendChild( defaultElem );
}
node.appendChild( defaultsElem );
// constraints
QDomElement constraintsElem = doc.createElement( QStringLiteral( "constraints" ) );
for ( const QgsField &field : std::as_const( mFields ) )
{
QDomElement constraintElem = doc.createElement( QStringLiteral( "constraint" ) );
constraintElem.setAttribute( QStringLiteral( "field" ), field.name() );
constraintElem.setAttribute( QStringLiteral( "constraints" ), field.constraints().constraints() );
constraintElem.setAttribute( QStringLiteral( "unique_strength" ), field.constraints().constraintStrength( QgsFieldConstraints::ConstraintUnique ) );
constraintElem.setAttribute( QStringLiteral( "notnull_strength" ), field.constraints().constraintStrength( QgsFieldConstraints::ConstraintNotNull ) );
constraintElem.setAttribute( QStringLiteral( "exp_strength" ), field.constraints().constraintStrength( QgsFieldConstraints::ConstraintExpression ) );
constraintsElem.appendChild( constraintElem );
}
node.appendChild( constraintsElem );
// constraint expressions
QDomElement constraintExpressionsElem = doc.createElement( QStringLiteral( "constraintExpressions" ) );
for ( const QgsField &field : std::as_const( mFields ) )
{
QDomElement constraintExpressionElem = doc.createElement( QStringLiteral( "constraint" ) );
constraintExpressionElem.setAttribute( QStringLiteral( "field" ), field.name() );
constraintExpressionElem.setAttribute( QStringLiteral( "exp" ), field.constraints().constraintExpression() );
constraintExpressionElem.setAttribute( QStringLiteral( "desc" ), field.constraints().constraintDescription() );
constraintExpressionsElem.appendChild( constraintExpressionElem );
}
node.appendChild( constraintExpressionsElem );
// save expression fields
if ( !mExpressionFieldBuffer )
{
// can happen when saving style on a invalid layer
QgsExpressionFieldBuffer dummy;
dummy.writeXml( node, doc );
}
else
{
mExpressionFieldBuffer->writeXml( node, doc );
}
}
// add attribute actions
if ( categories.testFlag( Actions ) )
mActions->writeXml( node );
if ( categories.testFlag( AttributeTable ) )
{
mAttributeTableConfig.writeXml( node );
mConditionalStyles->writeXml( node, doc, context );
mStoredExpressionManager->writeXml( node );
}
if ( categories.testFlag( Forms ) )
mEditFormConfig.writeXml( node, context );
// save readonly state
if ( categories.testFlag( LayerConfiguration ) )
node.toElement().setAttribute( QStringLiteral( "readOnly" ), mReadOnly );
// save preview expression
if ( categories.testFlag( LayerConfiguration ) )
{
QDomElement prevExpElem = doc.createElement( QStringLiteral( "previewExpression" ) );
QDomText prevExpText = doc.createTextNode( mDisplayExpression );
prevExpElem.appendChild( prevExpText );
node.appendChild( prevExpElem );
}
// save map tip
if ( categories.testFlag( MapTips ) )
{
QDomElement mapTipElem = doc.createElement( QStringLiteral( "mapTip" ) );
mapTipElem.setAttribute( QStringLiteral( "enabled" ), mapTipsEnabled() );
QDomText mapTipText = doc.createTextNode( mMapTipTemplate );
mapTipElem.appendChild( mapTipText );
node.toElement().appendChild( mapTipElem );
}
return true;
}
bool QgsVectorLayer::writeStyle( QDomNode &node, QDomDocument &doc, QString &errorMessage,
const QgsReadWriteContext &context, QgsMapLayer::StyleCategories categories ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QDomElement mapLayerNode = node.toElement();
emit writeCustomSymbology( mapLayerNode, doc, errorMessage );
// we must try to write the renderer if our geometry type is unknown
// as this allows the renderer to be correctly restored even for layers
// with broken sources
if ( isSpatial() || mWkbType == Qgis::WkbType::Unknown )
{
if ( categories.testFlag( Symbology ) )
{
if ( mRenderer )
{
QDomElement rendererElement = mRenderer->save( doc, context );
node.appendChild( rendererElement );
}
if ( mSelectionProperties )
{
mSelectionProperties->writeXml( mapLayerNode, doc, context );
}
}
if ( categories.testFlag( Labeling ) )
{
if ( mLabeling )
{
QDomElement labelingElement = mLabeling->save( doc, context );
node.appendChild( labelingElement );
}
mapLayerNode.setAttribute( QStringLiteral( "labelsEnabled" ), mLabelsEnabled ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
}
// save the simplification drawing settings
if ( categories.testFlag( Rendering ) )
{
mapLayerNode.setAttribute( QStringLiteral( "simplifyDrawingHints" ), QString::number( static_cast< int >( mSimplifyMethod.simplifyHints() ) ) );
mapLayerNode.setAttribute( QStringLiteral( "simplifyAlgorithm" ), QString::number( static_cast< int >( mSimplifyMethod.simplifyAlgorithm() ) ) );
mapLayerNode.setAttribute( QStringLiteral( "simplifyDrawingTol" ), QString::number( mSimplifyMethod.threshold() ) );
mapLayerNode.setAttribute( QStringLiteral( "simplifyLocal" ), mSimplifyMethod.forceLocalOptimization() ? 1 : 0 );
mapLayerNode.setAttribute( QStringLiteral( "simplifyMaxScale" ), QString::number( mSimplifyMethod.maximumScale() ) );
}
//save customproperties
if ( categories.testFlag( CustomProperties ) )
{
writeCustomProperties( node, doc );
}
if ( categories.testFlag( Symbology ) )
{
// add the blend mode field
QDomElement blendModeElem = doc.createElement( QStringLiteral( "blendMode" ) );
QDomText blendModeText = doc.createTextNode( QString::number( static_cast< int >( QgsPainting::getBlendModeEnum( blendMode() ) ) ) );
blendModeElem.appendChild( blendModeText );
node.appendChild( blendModeElem );
// add the feature blend mode field
QDomElement featureBlendModeElem = doc.createElement( QStringLiteral( "featureBlendMode" ) );
QDomText featureBlendModeText = doc.createTextNode( QString::number( static_cast< int >( QgsPainting::getBlendModeEnum( featureBlendMode() ) ) ) );
featureBlendModeElem.appendChild( featureBlendModeText );
node.appendChild( featureBlendModeElem );
}
// add the layer opacity and scale visibility
if ( categories.testFlag( Rendering ) )
{
QDomElement layerOpacityElem = doc.createElement( QStringLiteral( "layerOpacity" ) );
QDomText layerOpacityText = doc.createTextNode( QString::number( opacity() ) );
layerOpacityElem.appendChild( layerOpacityText );
node.appendChild( layerOpacityElem );
mapLayerNode.setAttribute( QStringLiteral( "hasScaleBasedVisibilityFlag" ), hasScaleBasedVisibility() ? 1 : 0 );
mapLayerNode.setAttribute( QStringLiteral( "maxScale" ), maximumScale() );
mapLayerNode.setAttribute( QStringLiteral( "minScale" ), minimumScale() );
mapLayerNode.setAttribute( QStringLiteral( "symbologyReferenceScale" ), mRenderer ? mRenderer->referenceScale() : -1 );
}
if ( categories.testFlag( Diagrams ) && mDiagramRenderer )
{
mDiagramRenderer->writeXml( mapLayerNode, doc, context );
if ( mDiagramLayerSettings )
mDiagramLayerSettings->writeXml( mapLayerNode, doc );
}
}
return true;
}
bool QgsVectorLayer::readSld( const QDomNode &node, QString &errorMessage )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
// get the Name element
QDomElement nameElem = node.firstChildElement( QStringLiteral( "Name" ) );
if ( nameElem.isNull() )
{
errorMessage = QStringLiteral( "Warning: Name element not found within NamedLayer while it's required." );
}
if ( isSpatial() )
{
QgsFeatureRenderer *r = QgsFeatureRenderer::loadSld( node, geometryType(), errorMessage );
if ( !r )
return false;
// defer style changed signal until we've set the renderer, labeling, everything.
// we don't want multiple signals!
ScopedIntIncrementor styleChangedSignalBlocker( &mBlockStyleChangedSignal );
setRenderer( r );
// labeling
readSldLabeling( node );
styleChangedSignalBlocker.release();
emitStyleChanged();
}
return true;
}
bool QgsVectorLayer::writeSld( QDomNode &node, QDomDocument &doc, QString &, const QVariantMap &props ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QgsSldExportContext context;
context.setExtraProperties( props );
writeSld( node, doc, context );
return true;
}
bool QgsVectorLayer::writeSld( QDomNode &node, QDomDocument &doc, QgsSldExportContext &context ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QVariantMap localProps = context.extraProperties();
if ( hasScaleBasedVisibility() )
{
QgsSymbolLayerUtils::mergeScaleDependencies( maximumScale(), minimumScale(), localProps );
}
context.setExtraProperties( localProps );
if ( isSpatial() )
{
// store the Name element
QDomElement nameNode = doc.createElement( QStringLiteral( "se:Name" ) );
nameNode.appendChild( doc.createTextNode( name() ) );
node.appendChild( nameNode );
QDomElement userStyleElem = doc.createElement( QStringLiteral( "UserStyle" ) );
node.appendChild( userStyleElem );
QDomElement nameElem = doc.createElement( QStringLiteral( "se:Name" ) );
nameElem.appendChild( doc.createTextNode( name() ) );
userStyleElem.appendChild( nameElem );
QDomElement featureTypeStyleElem = doc.createElement( QStringLiteral( "se:FeatureTypeStyle" ) );
userStyleElem.appendChild( featureTypeStyleElem );
mRenderer->toSld( doc, featureTypeStyleElem, context );
if ( labelsEnabled() )
{
mLabeling->toSld( featureTypeStyleElem, context );
}
}
return true;
}
bool QgsVectorLayer::changeGeometry( QgsFeatureId fid, QgsGeometry &geom, bool skipDefaultValue )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !mEditBuffer || !mDataProvider )
{
return false;
}
if ( mGeometryOptions->isActive() )
mGeometryOptions->apply( geom );
updateExtents();
bool result = mEditBuffer->changeGeometry( fid, geom );
if ( result )
{
updateExtents();
if ( !skipDefaultValue && !mDefaultValueOnUpdateFields.isEmpty() )
updateDefaultValues( fid );
}
return result;
}
bool QgsVectorLayer::changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue, bool skipDefaultValues, QgsVectorLayerToolsContext *context )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
bool result = false;
switch ( fields().fieldOrigin( field ) )
{
case Qgis::FieldOrigin::Join:
result = mJoinBuffer->changeAttributeValue( fid, field, newValue, oldValue );
if ( result )
emit attributeValueChanged( fid, field, newValue );
break;
case Qgis::FieldOrigin::Provider:
case Qgis::FieldOrigin::Edit:
case Qgis::FieldOrigin::Expression:
{
if ( mEditBuffer && mDataProvider )
result = mEditBuffer->changeAttributeValue( fid, field, newValue, oldValue );
break;
}
case Qgis::FieldOrigin::Unknown:
break;
}
if ( result && !skipDefaultValues && !mDefaultValueOnUpdateFields.isEmpty() )
updateDefaultValues( fid, QgsFeature(), context ? context->expressionContext() : nullptr );
return result;
}
bool QgsVectorLayer::changeAttributeValues( QgsFeatureId fid, const QgsAttributeMap &newValues, const QgsAttributeMap &oldValues, bool skipDefaultValues, QgsVectorLayerToolsContext *context )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
bool result = true;
QgsAttributeMap newValuesJoin;
QgsAttributeMap oldValuesJoin;
QgsAttributeMap newValuesNotJoin;
QgsAttributeMap oldValuesNotJoin;
for ( auto it = newValues.constBegin(); it != newValues.constEnd(); ++it )
{
const int field = it.key();
const QVariant newValue = it.value();
QVariant oldValue;
if ( oldValues.contains( field ) )
oldValue = oldValues[field];
switch ( fields().fieldOrigin( field ) )
{
case Qgis::FieldOrigin::Join:
newValuesJoin[field] = newValue;
oldValuesJoin[field] = oldValue;
break;
case Qgis::FieldOrigin::Provider:
case Qgis::FieldOrigin::Edit:
case Qgis::FieldOrigin::Expression:
{
newValuesNotJoin[field] = newValue;
oldValuesNotJoin[field] = oldValue;
break;
}
case Qgis::FieldOrigin::Unknown:
break;
}
}
if ( ! newValuesJoin.isEmpty() && mJoinBuffer )
{
result = mJoinBuffer->changeAttributeValues( fid, newValuesJoin, oldValuesJoin );
}
if ( ! newValuesNotJoin.isEmpty() )
{
if ( mEditBuffer && mDataProvider )
result &= mEditBuffer->changeAttributeValues( fid, newValuesNotJoin, oldValues );
else
result = false;
}
if ( result && !skipDefaultValues && !mDefaultValueOnUpdateFields.isEmpty() )
{
updateDefaultValues( fid, QgsFeature(), context ? context->expressionContext() : nullptr );
}
return result;
}
bool QgsVectorLayer::addAttribute( const QgsField &field )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !mEditBuffer || !mDataProvider )
return false;
return mEditBuffer->addAttribute( field );
}
void QgsVectorLayer::removeFieldAlias( int attIndex )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( attIndex < 0 || attIndex >= fields().count() )
return;
QString name = fields().at( attIndex ).name();
mFields[ attIndex ].setAlias( QString() );
if ( mAttributeAliasMap.contains( name ) )
{
mAttributeAliasMap.remove( name );
updateFields();
mEditFormConfig.setFields( mFields );
emit layerModified();
}
}
bool QgsVectorLayer::renameAttribute( int index, const QString &newName )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( index < 0 || index >= fields().count() )
return false;
switch ( mFields.fieldOrigin( index ) )
{
case Qgis::FieldOrigin::Expression:
{
if ( mExpressionFieldBuffer )
{
int oi = mFields.fieldOriginIndex( index );
mExpressionFieldBuffer->renameExpression( oi, newName );
updateFields();
return true;
}
else
{
return false;
}
}
case Qgis::FieldOrigin::Provider:
case Qgis::FieldOrigin::Edit:
if ( !mEditBuffer || !mDataProvider )
return false;
return mEditBuffer->renameAttribute( index, newName );
case Qgis::FieldOrigin::Join:
case Qgis::FieldOrigin::Unknown:
return false;
}
return false; // avoid warning
}
void QgsVectorLayer::setFieldAlias( int attIndex, const QString &aliasString )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( attIndex < 0 || attIndex >= fields().count() )
return;
QString name = fields().at( attIndex ).name();
mAttributeAliasMap.insert( name, aliasString );
mFields[ attIndex ].setAlias( aliasString );
mEditFormConfig.setFields( mFields );
emit layerModified(); // TODO[MD]: should have a different signal?
}
QString QgsVectorLayer::attributeAlias( int index ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( index < 0 || index >= fields().count() )
return QString();
return fields().at( index ).alias();
}
QString QgsVectorLayer::attributeDisplayName( int index ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( index >= 0 && index < mFields.count() )
return mFields.at( index ).displayName();
else
return QString();
}
QgsStringMap QgsVectorLayer::attributeAliases() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return mAttributeAliasMap;
}
void QgsVectorLayer::setFieldSplitPolicy( int index, Qgis::FieldDomainSplitPolicy policy )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( index < 0 || index >= fields().count() )
return;
const QString name = fields().at( index ).name();
mAttributeSplitPolicy.insert( name, policy );
mFields[ index ].setSplitPolicy( policy );
mEditFormConfig.setFields( mFields );
emit layerModified(); // TODO[MD]: should have a different signal?
}
void QgsVectorLayer::setFieldDuplicatePolicy( int index, Qgis::FieldDuplicatePolicy policy )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( index < 0 || index >= fields().count() )
return;
const QString name = fields().at( index ).name();
mAttributeDuplicatePolicy.insert( name, policy );
mFields[ index ].setDuplicatePolicy( policy );
mEditFormConfig.setFields( mFields );
emit layerModified(); // TODO[MD]: should have a different signal?
}
void QgsVectorLayer::setFieldMergePolicy( int index, Qgis::FieldDomainMergePolicy policy )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( index < 0 || index >= fields().count() )
return;
const QString name = fields().at( index ).name();
mAttributeMergePolicy.insert( name, policy );
mFields[ index ].setMergePolicy( policy );
mEditFormConfig.setFields( mFields );
emit layerModified(); // TODO[MD]: should have a different signal?
}
QSet<QString> QgsVectorLayer::excludeAttributesWms() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QSet<QString> excludeList;
QMap< QString, Qgis::FieldConfigurationFlags >::const_iterator flagsIt = mFieldConfigurationFlags.constBegin();
for ( ; flagsIt != mFieldConfigurationFlags.constEnd(); ++flagsIt )
{
if ( flagsIt->testFlag( Qgis::FieldConfigurationFlag::HideFromWms ) )
{
excludeList << flagsIt.key();
}
}
return excludeList;
}
void QgsVectorLayer::setExcludeAttributesWms( const QSet<QString> &att )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QMap< QString, Qgis::FieldConfigurationFlags >::iterator flagsIt = mFieldConfigurationFlags.begin();
for ( ; flagsIt != mFieldConfigurationFlags.end(); ++flagsIt )
{
flagsIt->setFlag( Qgis::FieldConfigurationFlag::HideFromWms, att.contains( flagsIt.key() ) );
}
updateFields();
}
QSet<QString> QgsVectorLayer::excludeAttributesWfs() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QSet<QString> excludeList;
QMap< QString, Qgis::FieldConfigurationFlags >::const_iterator flagsIt = mFieldConfigurationFlags.constBegin();
for ( ; flagsIt != mFieldConfigurationFlags.constEnd(); ++flagsIt )
{
if ( flagsIt->testFlag( Qgis::FieldConfigurationFlag::HideFromWfs ) )
{
excludeList << flagsIt.key();
}
}
return excludeList;
}
void QgsVectorLayer::setExcludeAttributesWfs( const QSet<QString> &att )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QMap< QString, Qgis::FieldConfigurationFlags >::iterator flagsIt = mFieldConfigurationFlags.begin();
for ( ; flagsIt != mFieldConfigurationFlags.end(); ++flagsIt )
{
flagsIt->setFlag( Qgis::FieldConfigurationFlag::HideFromWfs, att.contains( flagsIt.key() ) );
}
updateFields();
}
bool QgsVectorLayer::deleteAttribute( int index )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( index < 0 || index >= fields().count() )
return false;
if ( mFields.fieldOrigin( index ) == Qgis::FieldOrigin::Expression )
{
removeExpressionField( index );
return true;
}
if ( !mEditBuffer || !mDataProvider )
return false;
return mEditBuffer->deleteAttribute( index );
}
bool QgsVectorLayer::deleteAttributes( const QList<int> &attrs )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
bool deleted = false;
// Remove multiple occurrences of same attribute
QList<int> attrList = qgis::setToList( qgis::listToSet( attrs ) );
std::sort( attrList.begin(), attrList.end(), std::greater<int>() );
for ( int attr : std::as_const( attrList ) )
{
if ( deleteAttribute( attr ) )
{
deleted = true;
}
}
return deleted;
}
bool QgsVectorLayer::deleteFeatureCascade( QgsFeatureId fid, QgsVectorLayer::DeleteContext *context )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !mEditBuffer )
return false;
if ( context && context->cascade )
{
const QList<QgsRelation> relations = context->project->relationManager()->referencedRelations( this );
const bool hasRelationsOrJoins = !relations.empty() || mJoinBuffer->containsJoins();
if ( hasRelationsOrJoins )
{
if ( context->mHandledFeatures.contains( this ) )
{
QgsFeatureIds &handledFeatureIds = context->mHandledFeatures[ this ];
if ( handledFeatureIds.contains( fid ) )
{
// avoid endless recursion
return false;
}
else
{
// add feature id
handledFeatureIds << fid;
}
}
else
{
// add layer and feature id
context->mHandledFeatures.insert( this, QgsFeatureIds() << fid );
}
for ( const QgsRelation &relation : relations )
{
//check if composition (and not association)
switch ( relation.strength() )
{
case Qgis::RelationshipStrength::Composition:
{
//get features connected over this relation
QgsFeatureIterator relatedFeaturesIt = relation.getRelatedFeatures( getFeature( fid ) );
QgsFeatureIds childFeatureIds;
QgsFeature childFeature;
while ( relatedFeaturesIt.nextFeature( childFeature ) )
{
childFeatureIds.insert( childFeature.id() );
}
if ( childFeatureIds.count() > 0 )
{
relation.referencingLayer()->startEditing();
relation.referencingLayer()->deleteFeatures( childFeatureIds, context );
}
break;
}
case Qgis::RelationshipStrength::Association:
break;
}
}
}
}
if ( mJoinBuffer->containsJoins() )
mJoinBuffer->deleteFeature( fid, context );
bool res = mEditBuffer->deleteFeature( fid );
return res;
}
bool QgsVectorLayer::deleteFeature( QgsFeatureId fid, QgsVectorLayer::DeleteContext *context )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !mEditBuffer )
return false;
return deleteFeatureCascade( fid, context );
}
bool QgsVectorLayer::deleteFeatures( const QgsFeatureIds &fids, QgsVectorLayer::DeleteContext *context )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
bool res = true;
if ( ( context && context->cascade ) || mJoinBuffer->containsJoins() )
{
// should ideally be "deleteFeaturesCascade" for performance!
for ( QgsFeatureId fid : fids )
res = deleteFeatureCascade( fid, context ) && res;
}
else
{
res = mEditBuffer && mEditBuffer->deleteFeatures( fids );
}
if ( res )
{
mSelectedFeatureIds.subtract( fids ); // remove it from selection
updateExtents();
}
return res;
}
QgsFields QgsVectorLayer::fields() const
{
// non fatal for now -- the "rasterize" processing algorithm is not thread safe and calls this
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
return mFields;
}
QgsAttributeList QgsVectorLayer::primaryKeyAttributes() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QgsAttributeList pkAttributesList;
if ( !mDataProvider )
return pkAttributesList;
QgsAttributeList providerIndexes = mDataProvider->pkAttributeIndexes();
for ( int i = 0; i < mFields.count(); ++i )
{
if ( mFields.fieldOrigin( i ) == Qgis::FieldOrigin::Provider &&
providerIndexes.contains( mFields.fieldOriginIndex( i ) ) )
pkAttributesList << i;
}
return pkAttributesList;
}
long long QgsVectorLayer::featureCount() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !mDataProvider )
return static_cast< long long >( Qgis::FeatureCountState::UnknownCount );
return mDataProvider->featureCount() +
( mEditBuffer && ! mDataProvider->transaction() ? mEditBuffer->addedFeatures().size() - mEditBuffer->deletedFeatureIds().size() : 0 );
}
Qgis::FeatureAvailability QgsVectorLayer::hasFeatures() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
const QgsFeatureIds deletedFeatures( mEditBuffer && ! mDataProvider->transaction() ? mEditBuffer->deletedFeatureIds() : QgsFeatureIds() );
const QgsFeatureMap addedFeatures( mEditBuffer && ! mDataProvider->transaction() ? mEditBuffer->addedFeatures() : QgsFeatureMap() );
if ( mEditBuffer && !deletedFeatures.empty() )
{
if ( addedFeatures.size() > deletedFeatures.size() )
return Qgis::FeatureAvailability::FeaturesAvailable;
else
return Qgis::FeatureAvailability::FeaturesMaybeAvailable;
}
if ( ( !mEditBuffer || addedFeatures.empty() ) && mDataProvider && mDataProvider->empty() )
return Qgis::FeatureAvailability::NoFeaturesAvailable;
else
return Qgis::FeatureAvailability::FeaturesAvailable;
}
bool QgsVectorLayer::commitChanges( bool stopEditing )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( project() && project()->transactionMode() == Qgis::TransactionMode::BufferedGroups )
return project()->commitChanges( mCommitErrors, stopEditing, this );
mCommitErrors.clear();
if ( !mDataProvider )
{
mCommitErrors << tr( "ERROR: no provider" );
return false;
}
if ( !mEditBuffer )
{
mCommitErrors << tr( "ERROR: layer not editable" );
return false;
}
emit beforeCommitChanges( stopEditing );
if ( !mAllowCommit )
return false;
mCommitChangesActive = true;
bool success = false;
if ( mEditBuffer->editBufferGroup() )
success = mEditBuffer->editBufferGroup()->commitChanges( mCommitErrors, stopEditing );
else
success = mEditBuffer->commitChanges( mCommitErrors );
mCommitChangesActive = false;
if ( !mDeletedFids.empty() )
{
emit featuresDeleted( mDeletedFids );
mDeletedFids.clear();
}
if ( success )
{
if ( stopEditing )
{
clearEditBuffer();
}
undoStack()->clear();
emit afterCommitChanges();
if ( stopEditing )
emit editingStopped();
}
else
{
QgsMessageLog::logMessage( tr( "Commit errors:\n %1" ).arg( mCommitErrors.join( QLatin1String( "\n " ) ) ) );
}
updateFields();
mDataProvider->updateExtents();
if ( stopEditing )
{
mDataProvider->leaveUpdateMode();
}
// This second call is required because OGR provider with JSON
// driver might have changed fields order after the call to
// leaveUpdateMode
if ( mFields.names() != mDataProvider->fields().names() )
{
updateFields();
}
triggerRepaint();
return success;
}
QStringList QgsVectorLayer::commitErrors() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return mCommitErrors;
}
bool QgsVectorLayer::rollBack( bool deleteBuffer )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( project() && project()->transactionMode() == Qgis::TransactionMode::BufferedGroups )
return project()->rollBack( mCommitErrors, deleteBuffer, this );
if ( !mEditBuffer )
{
return false;
}
if ( !mDataProvider )
{
mCommitErrors << tr( "ERROR: no provider" );
return false;
}
bool rollbackExtent = !mDataProvider->transaction() && ( !mEditBuffer->deletedFeatureIds().isEmpty() ||
!mEditBuffer->addedFeatures().isEmpty() ||
!mEditBuffer->changedGeometries().isEmpty() );
emit beforeRollBack();
mEditBuffer->rollBack();
emit afterRollBack();
if ( isModified() )
{
// new undo stack roll back method
// old method of calling every undo could cause many canvas refreshes
undoStack()->setIndex( 0 );
}
updateFields();
if ( deleteBuffer )
{
delete mEditBuffer;
mEditBuffer = nullptr;
undoStack()->clear();
}
emit editingStopped();
if ( rollbackExtent )
updateExtents();
mDataProvider->leaveUpdateMode();
triggerRepaint();
return true;
}
int QgsVectorLayer::selectedFeatureCount() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return mSelectedFeatureIds.size();
}
const QgsFeatureIds &QgsVectorLayer::selectedFeatureIds() const
{
// non fatal for now -- the "rasterize" processing algorithm is not thread safe and calls this
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
return mSelectedFeatureIds;
}
QgsFeatureList QgsVectorLayer::selectedFeatures() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QgsFeatureList features;
features.reserve( mSelectedFeatureIds.count() );
QgsFeature f;
QgsFeatureIterator it = getSelectedFeatures();
while ( it.nextFeature( f ) )
{
features.push_back( f );
}
return features;
}
QgsFeatureIterator QgsVectorLayer::getSelectedFeatures( QgsFeatureRequest request ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( mSelectedFeatureIds.isEmpty() )
return QgsFeatureIterator();
if ( geometryType() == Qgis::GeometryType::Null )
request.setFlags( Qgis::FeatureRequestFlag::NoGeometry );
if ( mSelectedFeatureIds.count() == 1 )
request.setFilterFid( *mSelectedFeatureIds.constBegin() );
else
request.setFilterFids( mSelectedFeatureIds );
return getFeatures( request );
}
bool QgsVectorLayer::addFeatures( QgsFeatureList &features, Flags )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !mEditBuffer || !mDataProvider )
return false;
if ( mGeometryOptions->isActive() )
{
for ( auto feature = features.begin(); feature != features.end(); ++feature )
{
QgsGeometry geom = feature->geometry();
mGeometryOptions->apply( geom );
feature->setGeometry( geom );
}
}
bool res = mEditBuffer->addFeatures( features );
updateExtents();
if ( res && mJoinBuffer->containsJoins() )
res = mJoinBuffer->addFeatures( features );
return res;
}
void QgsVectorLayer::setCoordinateSystem()
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
// if layer is not spatial, it has not CRS!
setCrs( ( isSpatial() && mDataProvider ) ? mDataProvider->crs() : QgsCoordinateReferenceSystem() );
}
QString QgsVectorLayer::displayField() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QgsExpression exp( displayExpression() );
if ( exp.isField() )
{
return static_cast<const QgsExpressionNodeColumnRef *>( exp.rootNode() )->name();
}
return QString();
}
void QgsVectorLayer::setDisplayExpression( const QString &displayExpression )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( mDisplayExpression == displayExpression )
return;
mDisplayExpression = displayExpression;
emit displayExpressionChanged();
}
QString QgsVectorLayer::displayExpression() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !mDisplayExpression.isEmpty() || mFields.isEmpty() )
{
return mDisplayExpression;
}
else
{
const QString candidateName = QgsVectorLayerUtils::guessFriendlyIdentifierField( mFields );
if ( !candidateName.isEmpty() )
{
return QgsExpression::quotedColumnRef( candidateName );
}
else
{
return QString();
}
}
}
bool QgsVectorLayer::hasMapTips() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
// display expressions are used as a fallback when no explicit map tip template is set
return mapTipsEnabled() && ( !mapTipTemplate().isEmpty() || !displayExpression().isEmpty() );
}
bool QgsVectorLayer::isEditable() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return ( mEditBuffer && mDataProvider );
}
bool QgsVectorLayer::isSpatial() const
{
// non fatal for now -- the "rasterize" processing algorithm is not thread safe and calls this
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
Qgis::GeometryType t = geometryType();
return t != Qgis::GeometryType::Null && t != Qgis::GeometryType::Unknown;
}
bool QgsVectorLayer::isReadOnly() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return mDataSourceReadOnly || mReadOnly;
}
bool QgsVectorLayer::setReadOnly( bool readonly )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
// exit if the layer is in editing mode
if ( readonly && mEditBuffer )
return false;
// exit if the data source is in read-only mode
if ( !readonly && mDataSourceReadOnly )
return false;
mReadOnly = readonly;
emit readOnlyChanged();
return true;
}
bool QgsVectorLayer::supportsEditing() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( ! mDataProvider )
return false;
if ( mDataSourceReadOnly )
return false;
return mDataProvider->capabilities() & QgsVectorDataProvider::EditingCapabilities && ! mReadOnly;
}
bool QgsVectorLayer::isModified() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
emit beforeModifiedCheck();
return mEditBuffer && mEditBuffer->isModified();
}
bool QgsVectorLayer::isAuxiliaryField( int index, int &srcIndex ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
bool auxiliaryField = false;
srcIndex = -1;
if ( !auxiliaryLayer() )
return auxiliaryField;
if ( index >= 0 && fields().fieldOrigin( index ) == Qgis::FieldOrigin::Join )
{
const QgsVectorLayerJoinInfo *info = mJoinBuffer->joinForFieldIndex( index, fields(), srcIndex );
if ( info && info->joinLayerId() == auxiliaryLayer()->id() )
auxiliaryField = true;
}
return auxiliaryField;
}
void QgsVectorLayer::setRenderer( QgsFeatureRenderer *r )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
// we must allow setting a renderer if our geometry type is unknown
// as this allows the renderer to be correctly set even for layers
// with broken sources
// (note that we allow REMOVING the renderer for non-spatial layers,
// e.g. to permit removing the renderer when the layer changes from
// a spatial layer to a non-spatial one)
if ( r && !isSpatial() && mWkbType != Qgis::WkbType::Unknown )
return;
if ( r != mRenderer )
{
delete mRenderer;
mRenderer = r;
mSymbolFeatureCounted = false;
mSymbolFeatureCountMap.clear();
mSymbolFeatureIdMap.clear();
if ( mRenderer )
{
const double refreshRate = QgsSymbolLayerUtils::rendererFrameRate( mRenderer );
if ( refreshRate <= 0 )
{
mRefreshRendererTimer->stop();
mRefreshRendererTimer->setInterval( 0 );
}
else
{
mRefreshRendererTimer->setInterval( 1000 / refreshRate );
mRefreshRendererTimer->start();
}
}
emit rendererChanged();
emitStyleChanged();
}
}
void QgsVectorLayer::addFeatureRendererGenerator( QgsFeatureRendererGenerator *generator )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( generator )
{
mRendererGenerators << generator;
}
}
void QgsVectorLayer::removeFeatureRendererGenerator( const QString &id )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
for ( int i = mRendererGenerators.count() - 1; i >= 0; --i )
{
if ( mRendererGenerators.at( i )->id() == id )
{
delete mRendererGenerators.at( i );
mRendererGenerators.removeAt( i );
}
}
}
QList<const QgsFeatureRendererGenerator *> QgsVectorLayer::featureRendererGenerators() const
{
// non fatal for now -- the "rasterize" processing algorithm is not thread safe and calls this
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
QList< const QgsFeatureRendererGenerator * > res;
for ( const QgsFeatureRendererGenerator *generator : mRendererGenerators )
res << generator;
return res;
}
void QgsVectorLayer::beginEditCommand( const QString &text )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !mDataProvider )
{
return;
}
if ( mDataProvider->transaction() )
{
QString ignoredError;
mDataProvider->transaction()->createSavepoint( ignoredError );
}
undoStack()->beginMacro( text );
mEditCommandActive = true;
emit editCommandStarted( text );
}
void QgsVectorLayer::endEditCommand()
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !mDataProvider )
{
return;
}
undoStack()->endMacro();
mEditCommandActive = false;
if ( !mDeletedFids.isEmpty() )
{
if ( selectedFeatureCount() > 0 )
{
mSelectedFeatureIds.subtract( mDeletedFids );
}
emit featuresDeleted( mDeletedFids );
mDeletedFids.clear();
}
emit editCommandEnded();
}
void QgsVectorLayer::destroyEditCommand()
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !mDataProvider )
{
return;
}
undoStack()->endMacro();
undoStack()->undo();
// it's not directly possible to pop the last command off the stack (the destroyed one)
// and delete, so we add a dummy obsolete command to force this to occur.
// Pushing the new command deletes the destroyed one, and since the new
// command is obsolete it's automatically deleted by the undo stack.
auto command = std::make_unique< QUndoCommand >();
command->setObsolete( true );
undoStack()->push( command.release() );
mEditCommandActive = false;
mDeletedFids.clear();
emit editCommandDestroyed();
}
bool QgsVectorLayer::addJoin( const QgsVectorLayerJoinInfo &joinInfo )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return mJoinBuffer->addJoin( joinInfo );
}
bool QgsVectorLayer::removeJoin( const QString &joinLayerId )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return mJoinBuffer->removeJoin( joinLayerId );
}
const QList< QgsVectorLayerJoinInfo > QgsVectorLayer::vectorJoins() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return mJoinBuffer->vectorJoins();
}
int QgsVectorLayer::addExpressionField( const QString &exp, const QgsField &fld )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
emit beforeAddingExpressionField( fld.name() );
mExpressionFieldBuffer->addExpression( exp, fld );
updateFields();
int idx = mFields.indexFromName( fld.name() );
emit attributeAdded( idx );
return idx;
}
void QgsVectorLayer::removeExpressionField( int index )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
emit beforeRemovingExpressionField( index );
int oi = mFields.fieldOriginIndex( index );
mExpressionFieldBuffer->removeExpression( oi );
updateFields();
emit attributeDeleted( index );
}
QString QgsVectorLayer::expressionField( int index ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( mFields.fieldOrigin( index ) != Qgis::FieldOrigin::Expression )
return QString();
int oi = mFields.fieldOriginIndex( index );
if ( oi < 0 || oi >= mExpressionFieldBuffer->expressions().size() )
return QString();
return mExpressionFieldBuffer->expressions().at( oi ).cachedExpression.expression();
}
void QgsVectorLayer::updateExpressionField( int index, const QString &exp )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
int oi = mFields.fieldOriginIndex( index );
mExpressionFieldBuffer->updateExpression( oi, exp );
}
void QgsVectorLayer::updateFields()
{
// non fatal for now -- the QgsVirtualLayerTask class is not thread safe and calls this
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
if ( !mDataProvider )
return;
QgsFields oldFields = mFields;
mFields = mDataProvider->fields();
// added / removed fields
if ( mEditBuffer )
mEditBuffer->updateFields( mFields );
// joined fields
if ( mJoinBuffer->containsJoins() )
mJoinBuffer->updateFields( mFields );
if ( mExpressionFieldBuffer )
mExpressionFieldBuffer->updateFields( mFields );
// set aliases and default values
for ( auto aliasIt = mAttributeAliasMap.constBegin(); aliasIt != mAttributeAliasMap.constEnd(); ++aliasIt )
{
int index = mFields.lookupField( aliasIt.key() );
if ( index < 0 )
continue;
mFields[ index ].setAlias( aliasIt.value() );
}
for ( auto splitPolicyIt = mAttributeSplitPolicy.constBegin(); splitPolicyIt != mAttributeSplitPolicy.constEnd(); ++splitPolicyIt )
{
int index = mFields.lookupField( splitPolicyIt.key() );
if ( index < 0 )
continue;
mFields[ index ].setSplitPolicy( splitPolicyIt.value() );
}
for ( auto duplicatePolicyIt = mAttributeDuplicatePolicy.constBegin(); duplicatePolicyIt != mAttributeDuplicatePolicy.constEnd(); ++duplicatePolicyIt )
{
int index = mFields.lookupField( duplicatePolicyIt.key() );
if ( index < 0 )
continue;
mFields[ index ].setDuplicatePolicy( duplicatePolicyIt.value() );
}
for ( auto mergePolicyIt = mAttributeMergePolicy.constBegin(); mergePolicyIt != mAttributeMergePolicy.constEnd(); ++mergePolicyIt )
{
int index = mFields.lookupField( mergePolicyIt.key() );
if ( index < 0 )
continue;
mFields[ index ].setMergePolicy( mergePolicyIt.value() );
}
// Update configuration flags
QMap< QString, Qgis::FieldConfigurationFlags >::const_iterator flagsIt = mFieldConfigurationFlags.constBegin();
for ( ; flagsIt != mFieldConfigurationFlags.constEnd(); ++flagsIt )
{
int index = mFields.lookupField( flagsIt.key() );
if ( index < 0 )
continue;
mFields[index].setConfigurationFlags( flagsIt.value() );
}
// Update default values
mDefaultValueOnUpdateFields.clear();
QMap< QString, QgsDefaultValue >::const_iterator defaultIt = mDefaultExpressionMap.constBegin();
for ( ; defaultIt != mDefaultExpressionMap.constEnd(); ++defaultIt )
{
int index = mFields.lookupField( defaultIt.key() );
if ( index < 0 )
continue;
mFields[ index ].setDefaultValueDefinition( defaultIt.value() );
if ( defaultIt.value().applyOnUpdate() )
mDefaultValueOnUpdateFields.insert( index );
}
QMap< QString, QgsFieldConstraints::Constraints >::const_iterator constraintIt = mFieldConstraints.constBegin();
for ( ; constraintIt != mFieldConstraints.constEnd(); ++constraintIt )
{
int index = mFields.lookupField( constraintIt.key() );
if ( index < 0 )
continue;
QgsFieldConstraints constraints = mFields.at( index ).constraints();
// always keep provider constraints intact
if ( !( constraints.constraints() & QgsFieldConstraints::ConstraintNotNull ) && ( constraintIt.value() & QgsFieldConstraints::ConstraintNotNull ) )
constraints.setConstraint( QgsFieldConstraints::ConstraintNotNull, QgsFieldConstraints::ConstraintOriginLayer );
if ( !( constraints.constraints() & QgsFieldConstraints::ConstraintUnique ) && ( constraintIt.value() & QgsFieldConstraints::ConstraintUnique ) )
constraints.setConstraint( QgsFieldConstraints::ConstraintUnique, QgsFieldConstraints::ConstraintOriginLayer );
if ( !( constraints.constraints() & QgsFieldConstraints::ConstraintExpression ) && ( constraintIt.value() & QgsFieldConstraints::ConstraintExpression ) )
constraints.setConstraint( QgsFieldConstraints::ConstraintExpression, QgsFieldConstraints::ConstraintOriginLayer );
mFields[ index ].setConstraints( constraints );
}
QMap< QString, QPair< QString, QString > >::const_iterator constraintExpIt = mFieldConstraintExpressions.constBegin();
for ( ; constraintExpIt != mFieldConstraintExpressions.constEnd(); ++constraintExpIt )
{
int index = mFields.lookupField( constraintExpIt.key() );
if ( index < 0 )
continue;
QgsFieldConstraints constraints = mFields.at( index ).constraints();
// always keep provider constraints intact
if ( constraints.constraintOrigin( QgsFieldConstraints::ConstraintExpression ) == QgsFieldConstraints::ConstraintOriginProvider )
continue;
constraints.setConstraintExpression( constraintExpIt.value().first, constraintExpIt.value().second );
mFields[ index ].setConstraints( constraints );
}
QMap< QPair< QString, QgsFieldConstraints::Constraint >, QgsFieldConstraints::ConstraintStrength >::const_iterator constraintStrengthIt = mFieldConstraintStrength.constBegin();
for ( ; constraintStrengthIt != mFieldConstraintStrength.constEnd(); ++constraintStrengthIt )
{
int index = mFields.lookupField( constraintStrengthIt.key().first );
if ( index < 0 )
continue;
QgsFieldConstraints constraints = mFields.at( index ).constraints();
// always keep provider constraints intact
if ( constraints.constraintOrigin( QgsFieldConstraints::ConstraintExpression ) == QgsFieldConstraints::ConstraintOriginProvider )
continue;
constraints.setConstraintStrength( constraintStrengthIt.key().second, constraintStrengthIt.value() );
mFields[ index ].setConstraints( constraints );
}
auto fieldWidgetIterator = mFieldWidgetSetups.constBegin();
for ( ; fieldWidgetIterator != mFieldWidgetSetups.constEnd(); ++ fieldWidgetIterator )
{
int index = mFields.indexOf( fieldWidgetIterator.key() );
if ( index < 0 )
continue;
mFields[index].setEditorWidgetSetup( fieldWidgetIterator.value() );
}
if ( oldFields != mFields )
{
emit updatedFields();
mEditFormConfig.setFields( mFields );
}
}
QVariant QgsVectorLayer::defaultValue( int index, const QgsFeature &feature, QgsExpressionContext *context ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( index < 0 || index >= mFields.count() || !mDataProvider )
return QVariant();
QString expression = mFields.at( index ).defaultValueDefinition().expression();
if ( expression.isEmpty() )
return mDataProvider->defaultValue( index );
QgsExpressionContext *evalContext = context;
std::unique_ptr< QgsExpressionContext > tempContext;
if ( !evalContext )
{
// no context passed, so we create a default one
tempContext.reset( new QgsExpressionContext( QgsExpressionContextUtils::globalProjectLayerScopes( this ) ) );
evalContext = tempContext.get();
}
if ( feature.isValid() )
{
QgsExpressionContextScope *featScope = new QgsExpressionContextScope();
featScope->setFeature( feature );
featScope->setFields( feature.fields() );
evalContext->appendScope( featScope );
}
QVariant val;
QgsExpression exp( expression );
exp.prepare( evalContext );
if ( exp.hasEvalError() )
{
QgsLogger::warning( "Error evaluating default value: " + exp.evalErrorString() );
}
else
{
val = exp.evaluate( evalContext );
}
if ( feature.isValid() )
{
delete evalContext->popScope();
}
return val;
}
void QgsVectorLayer::setDefaultValueDefinition( int index, const QgsDefaultValue &definition )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( index < 0 || index >= mFields.count() )
return;
if ( definition.isValid() )
{
mDefaultExpressionMap.insert( mFields.at( index ).name(), definition );
}
else
{
mDefaultExpressionMap.remove( mFields.at( index ).name() );
}
updateFields();
}
QgsDefaultValue QgsVectorLayer::defaultValueDefinition( int index ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( index < 0 || index >= mFields.count() )
return QgsDefaultValue();
else
return mFields.at( index ).defaultValueDefinition();
}
QSet<QVariant> QgsVectorLayer::uniqueValues( int index, int limit ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QSet<QVariant> uniqueValues;
if ( !mDataProvider )
{
return uniqueValues;
}
Qgis::FieldOrigin origin = mFields.fieldOrigin( index );
switch ( origin )
{
case Qgis::FieldOrigin::Unknown:
return uniqueValues;
case Qgis::FieldOrigin::Provider: //a provider field
{
uniqueValues = mDataProvider->uniqueValues( index, limit );
if ( mEditBuffer && ! mDataProvider->transaction() )
{
QSet<QString> vals;
const auto constUniqueValues = uniqueValues;
for ( const QVariant &v : constUniqueValues )
{
vals << v.toString();
}
QgsFeatureMap added = mEditBuffer->addedFeatures();
QMapIterator< QgsFeatureId, QgsFeature > addedIt( added );
while ( addedIt.hasNext() && ( limit < 0 || uniqueValues.count() < limit ) )
{
addedIt.next();
QVariant v = addedIt.value().attribute( index );
if ( v.isValid() )
{
QString vs = v.toString();
if ( !vals.contains( vs ) )
{
vals << vs;
uniqueValues << v;
}
}
}
QMapIterator< QgsFeatureId, QgsAttributeMap > it( mEditBuffer->changedAttributeValues() );
while ( it.hasNext() && ( limit < 0 || uniqueValues.count() < limit ) )
{
it.next();
QVariant v = it.value().value( index );
if ( v.isValid() )
{
QString vs = v.toString();
if ( !vals.contains( vs ) )
{
vals << vs;
uniqueValues << v;
}
}
}
}
return uniqueValues;
}
case Qgis::FieldOrigin::Edit:
// the layer is editable, but in certain cases it can still be avoided going through all features
if ( mDataProvider->transaction() || (
mEditBuffer->deletedFeatureIds().isEmpty() &&
mEditBuffer->addedFeatures().isEmpty() &&
!mEditBuffer->deletedAttributeIds().contains( index ) &&
mEditBuffer->changedAttributeValues().isEmpty() ) )
{
uniqueValues = mDataProvider->uniqueValues( index, limit );
return uniqueValues;
}
[[fallthrough]];
//we need to go through each feature
case Qgis::FieldOrigin::Join:
case Qgis::FieldOrigin::Expression:
{
QgsAttributeList attList;
attList << index;
QgsFeatureIterator fit = getFeatures( QgsFeatureRequest()
.setFlags( Qgis::FeatureRequestFlag::NoGeometry )
.setSubsetOfAttributes( attList ) );
QgsFeature f;
QVariant currentValue;
QHash<QString, QVariant> val;
while ( fit.nextFeature( f ) )
{
currentValue = f.attribute( index );
val.insert( currentValue.toString(), currentValue );
if ( limit >= 0 && val.size() >= limit )
{
break;
}
}
return qgis::listToSet( val.values() );
}
}
Q_ASSERT_X( false, "QgsVectorLayer::uniqueValues()", "Unknown source of the field!" );
return uniqueValues;
}
QStringList QgsVectorLayer::uniqueStringsMatching( int index, const QString &substring, int limit, QgsFeedback *feedback ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QStringList results;
if ( !mDataProvider )
{
return results;
}
Qgis::FieldOrigin origin = mFields.fieldOrigin( index );
switch ( origin )
{
case Qgis::FieldOrigin::Unknown:
return results;
case Qgis::FieldOrigin::Provider: //a provider field
{
results = mDataProvider->uniqueStringsMatching( index, substring, limit, feedback );
if ( mEditBuffer && ! mDataProvider->transaction() )
{
QgsFeatureMap added = mEditBuffer->addedFeatures();
QMapIterator< QgsFeatureId, QgsFeature > addedIt( added );
while ( addedIt.hasNext() && ( limit < 0 || results.count() < limit ) && ( !feedback || !feedback->isCanceled() ) )
{
addedIt.next();
QVariant v = addedIt.value().attribute( index );
if ( v.isValid() )
{
QString vs = v.toString();
if ( vs.contains( substring, Qt::CaseInsensitive ) && !results.contains( vs ) )
{
results << vs;
}
}
}
QMapIterator< QgsFeatureId, QgsAttributeMap > it( mEditBuffer->changedAttributeValues() );
while ( it.hasNext() && ( limit < 0 || results.count() < limit ) && ( !feedback || !feedback->isCanceled() ) )
{
it.next();
QVariant v = it.value().value( index );
if ( v.isValid() )
{
QString vs = v.toString();
if ( vs.contains( substring, Qt::CaseInsensitive ) && !results.contains( vs ) )
{
results << vs;
}
}
}
}
return results;
}
case Qgis::FieldOrigin::Edit:
// the layer is editable, but in certain cases it can still be avoided going through all features
if ( mDataProvider->transaction() || ( mEditBuffer->deletedFeatureIds().isEmpty() &&
mEditBuffer->addedFeatures().isEmpty() &&
!mEditBuffer->deletedAttributeIds().contains( index ) &&
mEditBuffer->changedAttributeValues().isEmpty() ) )
{
return mDataProvider->uniqueStringsMatching( index, substring, limit, feedback );
}
[[fallthrough]];
//we need to go through each feature
case Qgis::FieldOrigin::Join:
case Qgis::FieldOrigin::Expression:
{
QgsAttributeList attList;
attList << index;
QgsFeatureRequest request;
request.setSubsetOfAttributes( attList );
request.setFlags( Qgis::FeatureRequestFlag::NoGeometry );
QString fieldName = mFields.at( index ).name();
request.setFilterExpression( QStringLiteral( "\"%1\" ILIKE '%%2%'" ).arg( fieldName, substring ) );
QgsFeatureIterator fit = getFeatures( request );
QgsFeature f;
QString currentValue;
while ( fit.nextFeature( f ) )
{
currentValue = f.attribute( index ).toString();
if ( !results.contains( currentValue ) )
results << currentValue;
if ( ( limit >= 0 && results.size() >= limit ) || ( feedback && feedback->isCanceled() ) )
{
break;
}
}
return results;
}
}
Q_ASSERT_X( false, "QgsVectorLayer::uniqueStringsMatching()", "Unknown source of the field!" );
return results;
}
QVariant QgsVectorLayer::minimumValue( int index ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QVariant minimum;
minimumOrMaximumValue( index, &minimum, nullptr );
return minimum;
}
QVariant QgsVectorLayer::maximumValue( int index ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QVariant maximum;
minimumOrMaximumValue( index, nullptr, &maximum );
return maximum;
}
void QgsVectorLayer::minimumAndMaximumValue( int index, QVariant &minimum, QVariant &maximum ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
minimumOrMaximumValue( index, &minimum, &maximum );
}
void QgsVectorLayer::minimumOrMaximumValue( int index, QVariant *minimum, QVariant *maximum ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( minimum )
*minimum = QVariant();
if ( maximum )
*maximum = QVariant();
if ( !mDataProvider )
{
return;
}
Qgis::FieldOrigin origin = mFields.fieldOrigin( index );
switch ( origin )
{
case Qgis::FieldOrigin::Unknown:
{
return;
}
case Qgis::FieldOrigin::Provider: //a provider field
{
if ( minimum )
*minimum = mDataProvider->minimumValue( index );
if ( maximum )
*maximum = mDataProvider->maximumValue( index );
if ( mEditBuffer && ! mDataProvider->transaction() )
{
const QgsFeatureMap added = mEditBuffer->addedFeatures();
QMapIterator< QgsFeatureId, QgsFeature > addedIt( added );
while ( addedIt.hasNext() )
{
addedIt.next();
const QVariant v = addedIt.value().attribute( index );
if ( minimum && v.isValid() && qgsVariantLessThan( v, *minimum ) )
*minimum = v;
if ( maximum && v.isValid() && qgsVariantGreaterThan( v, *maximum ) )
*maximum = v;
}
QMapIterator< QgsFeatureId, QgsAttributeMap > it( mEditBuffer->changedAttributeValues() );
while ( it.hasNext() )
{
it.next();
const QVariant v = it.value().value( index );
if ( minimum && v.isValid() && qgsVariantLessThan( v, *minimum ) )
*minimum = v;
if ( maximum && v.isValid() && qgsVariantGreaterThan( v, *maximum ) )
*maximum = v;
}
}
return;
}
case Qgis::FieldOrigin::Edit:
{
// the layer is editable, but in certain cases it can still be avoided going through all features
if ( mDataProvider->transaction() || ( mEditBuffer->deletedFeatureIds().isEmpty() &&
mEditBuffer->addedFeatures().isEmpty() &&
!mEditBuffer->deletedAttributeIds().contains( index ) &&
mEditBuffer->changedAttributeValues().isEmpty() ) )
{
if ( minimum )
*minimum = mDataProvider->minimumValue( index );
if ( maximum )
*maximum = mDataProvider->maximumValue( index );
return;
}
}
[[fallthrough]];
// no choice but to go through all features
case Qgis::FieldOrigin::Expression:
case Qgis::FieldOrigin::Join:
{
// we need to go through each feature
QgsAttributeList attList;
attList << index;
QgsFeatureIterator fit = getFeatures( QgsFeatureRequest()
.setFlags( Qgis::FeatureRequestFlag::NoGeometry )
.setSubsetOfAttributes( attList ) );
QgsFeature f;
bool firstValue = true;
while ( fit.nextFeature( f ) )
{
const QVariant currentValue = f.attribute( index );
if ( QgsVariantUtils::isNull( currentValue ) )
continue;
if ( firstValue )
{
if ( minimum )
*minimum = currentValue;
if ( maximum )
*maximum = currentValue;
firstValue = false;
}
else
{
if ( minimum && currentValue.isValid() && qgsVariantLessThan( currentValue, *minimum ) )
*minimum = currentValue;
if ( maximum && currentValue.isValid() && qgsVariantGreaterThan( currentValue, *maximum ) )
*maximum = currentValue;
}
}
return;
}
}
Q_ASSERT_X( false, "QgsVectorLayer::minimumOrMaximumValue()", "Unknown source of the field!" );
}
void QgsVectorLayer::createEditBuffer()
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( mEditBuffer )
clearEditBuffer();
if ( mDataProvider->transaction() )
{
mEditBuffer = new QgsVectorLayerEditPassthrough( this );
connect( mDataProvider->transaction(), &QgsTransaction::dirtied, this, &QgsVectorLayer::onDirtyTransaction, Qt::UniqueConnection );
}
else
{
mEditBuffer = new QgsVectorLayerEditBuffer( this );
}
// forward signals
connect( mEditBuffer, &QgsVectorLayerEditBuffer::layerModified, this, &QgsVectorLayer::invalidateSymbolCountedFlag );
connect( mEditBuffer, &QgsVectorLayerEditBuffer::layerModified, this, &QgsVectorLayer::layerModified ); // TODO[MD]: necessary?
//connect( mEditBuffer, SIGNAL( layerModified() ), this, SLOT( triggerRepaint() ) ); // TODO[MD]: works well?
connect( mEditBuffer, &QgsVectorLayerEditBuffer::featureAdded, this, &QgsVectorLayer::onFeatureAdded );
connect( mEditBuffer, &QgsVectorLayerEditBuffer::featureDeleted, this, &QgsVectorLayer::onFeatureDeleted );
connect( mEditBuffer, &QgsVectorLayerEditBuffer::geometryChanged, this, &QgsVectorLayer::geometryChanged );
connect( mEditBuffer, &QgsVectorLayerEditBuffer::attributeValueChanged, this, &QgsVectorLayer::attributeValueChanged );
connect( mEditBuffer, &QgsVectorLayerEditBuffer::attributeAdded, this, &QgsVectorLayer::attributeAdded );
connect( mEditBuffer, &QgsVectorLayerEditBuffer::attributeDeleted, this, &QgsVectorLayer::attributeDeleted );
connect( mEditBuffer, &QgsVectorLayerEditBuffer::committedAttributesDeleted, this, &QgsVectorLayer::committedAttributesDeleted );
connect( mEditBuffer, &QgsVectorLayerEditBuffer::committedAttributesAdded, this, &QgsVectorLayer::committedAttributesAdded );
connect( mEditBuffer, &QgsVectorLayerEditBuffer::committedFeaturesAdded, this, &QgsVectorLayer::committedFeaturesAdded );
connect( mEditBuffer, &QgsVectorLayerEditBuffer::committedFeaturesRemoved, this, &QgsVectorLayer::committedFeaturesRemoved );
connect( mEditBuffer, &QgsVectorLayerEditBuffer::committedAttributeValuesChanges, this, &QgsVectorLayer::committedAttributeValuesChanges );
connect( mEditBuffer, &QgsVectorLayerEditBuffer::committedGeometriesChanges, this, &QgsVectorLayer::committedGeometriesChanges );
}
void QgsVectorLayer::clearEditBuffer()
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
delete mEditBuffer;
mEditBuffer = nullptr;
}
QVariant QgsVectorLayer::aggregate( Qgis::Aggregate aggregate, const QString &fieldOrExpression,
const QgsAggregateCalculator::AggregateParameters &parameters, QgsExpressionContext *context,
bool *ok, QgsFeatureIds *fids, QgsFeedback *feedback, QString *error ) const
{
// non fatal for now -- the aggregate expression functions are not thread safe and call this
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
if ( ok )
*ok = false;
if ( error )
error->clear();
if ( !mDataProvider )
{
if ( error )
*error = tr( "Layer is invalid" );
return QVariant();
}
// test if we are calculating based on a field
const int attrIndex = QgsExpression::expressionToLayerFieldIndex( fieldOrExpression, this );
if ( attrIndex >= 0 )
{
// aggregate is based on a field - if it's a provider field, we could possibly hand over the calculation
// to the provider itself
Qgis::FieldOrigin origin = mFields.fieldOrigin( attrIndex );
if ( origin == Qgis::FieldOrigin::Provider )
{
bool providerOk = false;
QVariant val = mDataProvider->aggregate( aggregate, attrIndex, parameters, context, providerOk, fids );
if ( providerOk )
{
// provider handled calculation
if ( ok )
*ok = true;
return val;
}
}
}
// fallback to using aggregate calculator to determine aggregate
QgsAggregateCalculator c( this );
if ( fids )
c.setFidsFilter( *fids );
c.setParameters( parameters );
bool aggregateOk = false;
const QVariant result = c.calculate( aggregate, fieldOrExpression, context, &aggregateOk, feedback );
if ( ok )
*ok = aggregateOk;
if ( !aggregateOk && error )
*error = c.lastError();
return result;
}
void QgsVectorLayer::setFeatureBlendMode( QPainter::CompositionMode featureBlendMode )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( mFeatureBlendMode == featureBlendMode )
return;
mFeatureBlendMode = featureBlendMode;
emit featureBlendModeChanged( featureBlendMode );
emitStyleChanged();
}
QPainter::CompositionMode QgsVectorLayer::featureBlendMode() const
{
// non fatal for now -- the "rasterize" processing algorithm is not thread safe and calls this
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
return mFeatureBlendMode;
}
void QgsVectorLayer::readSldLabeling( const QDomNode &node )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
setLabeling( nullptr ); // start with no labeling
setLabelsEnabled( false );
QDomElement element = node.toElement();
if ( element.isNull() )
return;
QDomElement userStyleElem = element.firstChildElement( QStringLiteral( "UserStyle" ) );
if ( userStyleElem.isNull() )
{
QgsDebugMsgLevel( QStringLiteral( "Info: UserStyle element not found." ), 4 );
return;
}
QDomElement featTypeStyleElem = userStyleElem.firstChildElement( QStringLiteral( "FeatureTypeStyle" ) );
if ( featTypeStyleElem.isNull() )
{
QgsDebugMsgLevel( QStringLiteral( "Info: FeatureTypeStyle element not found." ), 4 );
return;
}
// create empty FeatureTypeStyle element to merge TextSymbolizer's Rule's from all FeatureTypeStyle's
QDomElement mergedFeatTypeStyle = featTypeStyleElem.cloneNode( false ).toElement();
// use the RuleRenderer when more rules are present or the rule
// has filters or min/max scale denominators set,
// otherwise use the Simple labeling
bool needRuleBasedLabeling = false;
int ruleCount = 0;
while ( !featTypeStyleElem.isNull() )
{
QDomElement ruleElem = featTypeStyleElem.firstChildElement( QStringLiteral( "Rule" ) );
while ( !ruleElem.isNull() )
{
// test rule children element to check if we need to create RuleRenderer
// and if the rule has a symbolizer
bool hasTextSymbolizer = false;
bool hasRuleBased = false;
QDomElement ruleChildElem = ruleElem.firstChildElement();
while ( !ruleChildElem.isNull() )
{
// rule has filter or min/max scale denominator, use the RuleRenderer
if ( ruleChildElem.localName() == QLatin1String( "Filter" ) ||
ruleChildElem.localName() == QLatin1String( "MinScaleDenominator" ) ||
ruleChildElem.localName() == QLatin1String( "MaxScaleDenominator" ) )
{
hasRuleBased = true;
}
// rule has a renderer symbolizer, not a text symbolizer
else if ( ruleChildElem.localName() == QLatin1String( "TextSymbolizer" ) )
{
QgsDebugMsgLevel( QStringLiteral( "Info: TextSymbolizer element found" ), 4 );
hasTextSymbolizer = true;
}
ruleChildElem = ruleChildElem.nextSiblingElement();
}
if ( hasTextSymbolizer )
{
ruleCount++;
// append a clone of all Rules to the merged FeatureTypeStyle element
mergedFeatTypeStyle.appendChild( ruleElem.cloneNode().toElement() );
if ( hasRuleBased )
{
QgsDebugMsgLevel( QStringLiteral( "Info: Filter or Min/MaxScaleDenominator element found: need a RuleBasedLabeling" ), 4 );
needRuleBasedLabeling = true;
}
}
// more rules present, use the RuleRenderer
if ( ruleCount > 1 )
{
QgsDebugMsgLevel( QStringLiteral( "Info: More Rule elements found: need a RuleBasedLabeling" ), 4 );
needRuleBasedLabeling = true;
}
// not use the rule based labeling if no rules with textSymbolizer
if ( ruleCount == 0 )
{
needRuleBasedLabeling = false;
}
ruleElem = ruleElem.nextSiblingElement( QStringLiteral( "Rule" ) );
}
featTypeStyleElem = featTypeStyleElem.nextSiblingElement( QStringLiteral( "FeatureTypeStyle" ) );
}
if ( ruleCount == 0 )
{
QgsDebugMsgLevel( QStringLiteral( "Info: No TextSymbolizer element." ), 4 );
return;
}
QDomElement ruleElem = mergedFeatTypeStyle.firstChildElement( QStringLiteral( "Rule" ) );
if ( needRuleBasedLabeling )
{
QgsDebugMsgLevel( QStringLiteral( "Info: rule based labeling" ), 4 );
QgsRuleBasedLabeling::Rule *rootRule = new QgsRuleBasedLabeling::Rule( nullptr );
while ( !ruleElem.isNull() )
{
QString label, description, filterExp;
int scaleMinDenom = 0, scaleMaxDenom = 0;
QgsPalLayerSettings settings;
// retrieve the Rule element child nodes
QDomElement childElem = ruleElem.firstChildElement();
while ( !childElem.isNull() )
{
if ( childElem.localName() == QLatin1String( "Name" ) )
{
// <se:Name> tag contains the rule identifier,
// so prefer title tag for the label property value
if ( label.isEmpty() )
label = childElem.firstChild().nodeValue();
}
else if ( childElem.localName() == QLatin1String( "Description" ) )
{
// <se:Description> can contains a title and an abstract
QDomElement titleElem = childElem.firstChildElement( QStringLiteral( "Title" ) );
if ( !titleElem.isNull() )
{
label = titleElem.firstChild().nodeValue();
}
QDomElement abstractElem = childElem.firstChildElement( QStringLiteral( "Abstract" ) );
if ( !abstractElem.isNull() )
{
description = abstractElem.firstChild().nodeValue();
}
}
else if ( childElem.localName() == QLatin1String( "Abstract" ) )
{
// <sld:Abstract> (v1.0)
description = childElem.firstChild().nodeValue();
}
else if ( childElem.localName() == QLatin1String( "Title" ) )
{
// <sld:Title> (v1.0)
label = childElem.firstChild().nodeValue();
}
else if ( childElem.localName() == QLatin1String( "Filter" ) )
{
QgsExpression *filter = QgsOgcUtils::expressionFromOgcFilter( childElem );
if ( filter )
{
if ( filter->hasParserError() )
{
QgsDebugMsgLevel( QStringLiteral( "SLD Filter parsing error: %1" ).arg( filter->parserErrorString() ), 3 );
}
else
{
filterExp = filter->expression();
}
delete filter;
}
}
else if ( childElem.localName() == QLatin1String( "MinScaleDenominator" ) )
{
bool ok;
int v = childElem.firstChild().nodeValue().toInt( &ok );
if ( ok )
scaleMinDenom = v;
}
else if ( childElem.localName() == QLatin1String( "MaxScaleDenominator" ) )
{
bool ok;
int v = childElem.firstChild().nodeValue().toInt( &ok );
if ( ok )
scaleMaxDenom = v;
}
else if ( childElem.localName() == QLatin1String( "TextSymbolizer" ) )
{
readSldTextSymbolizer( childElem, settings );
}
childElem = childElem.nextSiblingElement();
}
QgsRuleBasedLabeling::Rule *ruleLabeling = new QgsRuleBasedLabeling::Rule( new QgsPalLayerSettings( settings ), scaleMinDenom, scaleMaxDenom, filterExp, label );
rootRule->appendChild( ruleLabeling );
ruleElem = ruleElem.nextSiblingElement();
}
setLabeling( new QgsRuleBasedLabeling( rootRule ) );
setLabelsEnabled( true );
}
else
{
QgsDebugMsgLevel( QStringLiteral( "Info: simple labeling" ), 4 );
// retrieve the TextSymbolizer element child node
QDomElement textSymbolizerElem = ruleElem.firstChildElement( QStringLiteral( "TextSymbolizer" ) );
QgsPalLayerSettings s;
if ( readSldTextSymbolizer( textSymbolizerElem, s ) )
{
setLabeling( new QgsVectorLayerSimpleLabeling( s ) );
setLabelsEnabled( true );
}
}
}
bool QgsVectorLayer::readSldTextSymbolizer( const QDomNode &node, QgsPalLayerSettings &settings ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( node.localName() != QLatin1String( "TextSymbolizer" ) )
{
QgsDebugMsgLevel( QStringLiteral( "Not a TextSymbolizer element: %1" ).arg( node.localName() ), 3 );
return false;
}
QDomElement textSymbolizerElem = node.toElement();
// Label
QDomElement labelElem = textSymbolizerElem.firstChildElement( QStringLiteral( "Label" ) );
if ( !labelElem.isNull() )
{
QDomElement propertyNameElem = labelElem.firstChildElement( QStringLiteral( "PropertyName" ) );
if ( !propertyNameElem.isNull() )
{
// set labeling defaults
// label attribute
QString labelAttribute = propertyNameElem.text();
settings.fieldName = labelAttribute;
settings.isExpression = false;
int fieldIndex = mFields.lookupField( labelAttribute );
if ( fieldIndex == -1 )
{
// label attribute is not in columns, check if it is an expression
QgsExpression exp( labelAttribute );
if ( !exp.hasEvalError() )
{
settings.isExpression = true;
}
else
{
QgsDebugMsgLevel( QStringLiteral( "SLD label attribute error: %1" ).arg( exp.evalErrorString() ), 3 );
}
}
}
else
{
QgsDebugMsgLevel( QStringLiteral( "Info: PropertyName element not found." ), 4 );
return false;
}
}
else
{
QgsDebugMsgLevel( QStringLiteral( "Info: Label element not found." ), 4 );
return false;
}
Qgis::RenderUnit sldUnitSize = Qgis::RenderUnit::Pixels;
if ( textSymbolizerElem.hasAttribute( QStringLiteral( "uom" ) ) )
{
sldUnitSize = QgsSymbolLayerUtils::decodeSldUom( textSymbolizerElem.attribute( QStringLiteral( "uom" ) ) );
}
QString fontFamily = QStringLiteral( "Sans-Serif" );
int fontPointSize = 10;
Qgis::RenderUnit fontUnitSize = Qgis::RenderUnit::Points;
int fontWeight = -1;
bool fontItalic = false;
bool fontUnderline = false;
// Font
QDomElement fontElem = textSymbolizerElem.firstChildElement( QStringLiteral( "Font" ) );
if ( !fontElem.isNull() )
{
QgsStringMap fontSvgParams = QgsSymbolLayerUtils::getSvgParameterList( fontElem );
for ( QgsStringMap::iterator it = fontSvgParams.begin(); it != fontSvgParams.end(); ++it )
{
QgsDebugMsgLevel( QStringLiteral( "found fontSvgParams %1: %2" ).arg( it.key(), it.value() ), 4 );
if ( it.key() == QLatin1String( "font-family" ) )
{
fontFamily = it.value();
}
else if ( it.key() == QLatin1String( "font-style" ) )
{
fontItalic = ( it.value() == QLatin1String( "italic" ) ) || ( it.value() == QLatin1String( "Italic" ) );
}
else if ( it.key() == QLatin1String( "font-size" ) )
{
bool ok;
int fontSize = it.value().toInt( &ok );
if ( ok )
{
fontPointSize = fontSize;
fontUnitSize = sldUnitSize;
}
}
else if ( it.key() == QLatin1String( "font-weight" ) )
{
if ( ( it.value() == QLatin1String( "bold" ) ) || ( it.value() == QLatin1String( "Bold" ) ) )
fontWeight = QFont::Bold;
}
else if ( it.key() == QLatin1String( "font-underline" ) )
{
fontUnderline = ( it.value() == QLatin1String( "underline" ) ) || ( it.value() == QLatin1String( "Underline" ) );
}
}
}
QgsTextFormat format;
QFont font( fontFamily, fontPointSize, fontWeight, fontItalic );
font.setUnderline( fontUnderline );
format.setFont( font );
format.setSize( fontPointSize );
format.setSizeUnit( fontUnitSize );
// Fill
QDomElement fillElem = textSymbolizerElem.firstChildElement( QStringLiteral( "Fill" ) );
QColor textColor;
Qt::BrushStyle textBrush = Qt::SolidPattern;
QgsSymbolLayerUtils::fillFromSld( fillElem, textBrush, textColor );
if ( textColor.isValid() )
{
QgsDebugMsgLevel( QStringLiteral( "Info: textColor %1." ).arg( QVariant( textColor ).toString() ), 4 );
format.setColor( textColor );
}
QgsTextBufferSettings bufferSettings;
// Halo
QDomElement haloElem = textSymbolizerElem.firstChildElement( QStringLiteral( "Halo" ) );
if ( !haloElem.isNull() )
{
bufferSettings.setEnabled( true );
bufferSettings.setSize( 1 );
QDomElement radiusElem = haloElem.firstChildElement( QStringLiteral( "Radius" ) );
if ( !radiusElem.isNull() )
{
bool ok;
double bufferSize = radiusElem.text().toDouble( &ok );
if ( ok )
{
bufferSettings.setSize( bufferSize );
bufferSettings.setSizeUnit( sldUnitSize );
}
}
QDomElement haloFillElem = haloElem.firstChildElement( QStringLiteral( "Fill" ) );
QColor bufferColor;
Qt::BrushStyle bufferBrush = Qt::SolidPattern;
QgsSymbolLayerUtils::fillFromSld( haloFillElem, bufferBrush, bufferColor );
if ( bufferColor.isValid() )
{
QgsDebugMsgLevel( QStringLiteral( "Info: bufferColor %1." ).arg( QVariant( bufferColor ).toString() ), 4 );
bufferSettings.setColor( bufferColor );
}
}
// LabelPlacement
QDomElement labelPlacementElem = textSymbolizerElem.firstChildElement( QStringLiteral( "LabelPlacement" ) );
if ( !labelPlacementElem.isNull() )
{
// PointPlacement
QDomElement pointPlacementElem = labelPlacementElem.firstChildElement( QStringLiteral( "PointPlacement" ) );
if ( !pointPlacementElem.isNull() )
{
settings.placement = Qgis::LabelPlacement::OverPoint;
if ( geometryType() == Qgis::GeometryType::Line )
{
settings.placement = Qgis::LabelPlacement::Horizontal;
}
QDomElement displacementElem = pointPlacementElem.firstChildElement( QStringLiteral( "Displacement" ) );
if ( !displacementElem.isNull() )
{
QDomElement displacementXElem = displacementElem.firstChildElement( QStringLiteral( "DisplacementX" ) );
if ( !displacementXElem.isNull() )
{
bool ok;
double xOffset = displacementXElem.text().toDouble( &ok );
if ( ok )
{
settings.xOffset = xOffset;
settings.offsetUnits = sldUnitSize;
}
}
QDomElement displacementYElem = displacementElem.firstChildElement( QStringLiteral( "DisplacementY" ) );
if ( !displacementYElem.isNull() )
{
bool ok;
double yOffset = displacementYElem.text().toDouble( &ok );
if ( ok )
{
settings.yOffset = yOffset;
settings.offsetUnits = sldUnitSize;
}
}
}
QDomElement anchorPointElem = pointPlacementElem.firstChildElement( QStringLiteral( "AnchorPoint" ) );
if ( !anchorPointElem.isNull() )
{
QDomElement anchorPointXElem = anchorPointElem.firstChildElement( QStringLiteral( "AnchorPointX" ) );
if ( !anchorPointXElem.isNull() )
{
bool ok;
double xOffset = anchorPointXElem.text().toDouble( &ok );
if ( ok )
{
settings.xOffset = xOffset;
settings.offsetUnits = sldUnitSize;
}
}
QDomElement anchorPointYElem = anchorPointElem.firstChildElement( QStringLiteral( "AnchorPointY" ) );
if ( !anchorPointYElem.isNull() )
{
bool ok;
double yOffset = anchorPointYElem.text().toDouble( &ok );
if ( ok )
{
settings.yOffset = yOffset;
settings.offsetUnits = sldUnitSize;
}
}
}
QDomElement rotationElem = pointPlacementElem.firstChildElement( QStringLiteral( "Rotation" ) );
if ( !rotationElem.isNull() )
{
bool ok;
double rotation = rotationElem.text().toDouble( &ok );
if ( ok )
{
settings.angleOffset = 360 - rotation;
}
}
}
else
{
// PointPlacement
QDomElement linePlacementElem = labelPlacementElem.firstChildElement( QStringLiteral( "LinePlacement" ) );
if ( !linePlacementElem.isNull() )
{
settings.placement = Qgis::LabelPlacement::Line;
}
}
}
// read vendor options
QgsStringMap vendorOptions;
QDomElement vendorOptionElem = textSymbolizerElem.firstChildElement( QStringLiteral( "VendorOption" ) );
while ( !vendorOptionElem.isNull() && vendorOptionElem.localName() == QLatin1String( "VendorOption" ) )
{
QString optionName = vendorOptionElem.attribute( QStringLiteral( "name" ) );
QString optionValue;
if ( vendorOptionElem.firstChild().nodeType() == QDomNode::TextNode )
{
optionValue = vendorOptionElem.firstChild().nodeValue();
}
else
{
if ( vendorOptionElem.firstChild().nodeType() == QDomNode::ElementNode &&
vendorOptionElem.firstChild().localName() == QLatin1String( "Literal" ) )
{
QgsDebugMsgLevel( vendorOptionElem.firstChild().localName(), 2 );
optionValue = vendorOptionElem.firstChild().firstChild().nodeValue();
}
else
{
QgsDebugError( QStringLiteral( "unexpected child of %1 named %2" ).arg( vendorOptionElem.localName(), optionName ) );
}
}
if ( !optionName.isEmpty() && !optionValue.isEmpty() )
{
vendorOptions[ optionName ] = optionValue;
}
vendorOptionElem = vendorOptionElem.nextSiblingElement();
}
if ( !vendorOptions.isEmpty() )
{
for ( QgsStringMap::iterator it = vendorOptions.begin(); it != vendorOptions.end(); ++it )
{
if ( it.key() == QLatin1String( "underlineText" ) && it.value() == QLatin1String( "true" ) )
{
font.setUnderline( true );
format.setFont( font );
}
else if ( it.key() == QLatin1String( "strikethroughText" ) && it.value() == QLatin1String( "true" ) )
{
font.setStrikeOut( true );
format.setFont( font );
}
else if ( it.key() == QLatin1String( "maxDisplacement" ) )
{
settings.placement = Qgis::LabelPlacement::AroundPoint;
}
else if ( it.key() == QLatin1String( "followLine" ) && it.value() == QLatin1String( "true" ) )
{
if ( geometryType() == Qgis::GeometryType::Polygon )
{
settings.placement = Qgis::LabelPlacement::PerimeterCurved;
}
else
{
settings.placement = Qgis::LabelPlacement::Curved;
}
}
else if ( it.key() == QLatin1String( "maxAngleDelta" ) )
{
bool ok;
double angle = it.value().toDouble( &ok );
if ( ok )
{
settings.maxCurvedCharAngleIn = angle;
settings.maxCurvedCharAngleOut = angle;
}
}
// miscellaneous options
else if ( it.key() == QLatin1String( "conflictResolution" ) && it.value() == QLatin1String( "false" ) )
{
settings.placementSettings().setOverlapHandling( Qgis::LabelOverlapHandling::AllowOverlapIfRequired );
}
else if ( it.key() == QLatin1String( "forceLeftToRight" ) && it.value() == QLatin1String( "false" ) )
{
settings.upsidedownLabels = Qgis::UpsideDownLabelHandling::AlwaysAllowUpsideDown;
}
else if ( it.key() == QLatin1String( "group" ) && it.value() == QLatin1String( "yes" ) )
{
settings.lineSettings().setMergeLines( true );
}
else if ( it.key() == QLatin1String( "labelAllGroup" ) && it.value() == QLatin1String( "true" ) )
{
settings.lineSettings().setMergeLines( true );
}
}
}
format.setBuffer( bufferSettings );
settings.setFormat( format );
return true;
}
QgsEditFormConfig QgsVectorLayer::editFormConfig() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return mEditFormConfig;
}
void QgsVectorLayer::setEditFormConfig( const QgsEditFormConfig &editFormConfig )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( mEditFormConfig == editFormConfig )
return;
mEditFormConfig = editFormConfig;
mEditFormConfig.onRelationsLoaded();
emit editFormConfigChanged();
}
QgsAttributeTableConfig QgsVectorLayer::attributeTableConfig() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QgsAttributeTableConfig config = mAttributeTableConfig;
if ( config.isEmpty() )
config.update( fields() );
return config;
}
void QgsVectorLayer::setAttributeTableConfig( const QgsAttributeTableConfig &attributeTableConfig )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( mAttributeTableConfig != attributeTableConfig )
{
mAttributeTableConfig = attributeTableConfig;
emit configChanged();
}
}
QgsExpressionContext QgsVectorLayer::createExpressionContext() const
{
// called in a non-thread-safe way in some cases when calculating aggregates in a different thread
QGIS_PROTECT_QOBJECT_THREAD_ACCESS_NON_FATAL
return QgsExpressionContext( QgsExpressionContextUtils::globalProjectLayerScopes( this ) );
}
QgsExpressionContextScope *QgsVectorLayer::createExpressionContextScope() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return QgsExpressionContextUtils::layerScope( this );
}
void QgsVectorLayer::setDiagramLayerSettings( const QgsDiagramLayerSettings &s )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( !mDiagramLayerSettings )
mDiagramLayerSettings = new QgsDiagramLayerSettings();
*mDiagramLayerSettings = s;
}
QString QgsVectorLayer::htmlMetadata() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QgsLayerMetadataFormatter htmlFormatter( metadata() );
QString myMetadata = QStringLiteral( "<html><head></head>\n<body>\n" );
myMetadata += generalHtmlMetadata();
// Begin Provider section
myMetadata += QStringLiteral( "<h1>" ) + tr( "Information from provider" ) + QStringLiteral( "</h1>\n<hr>\n" );
myMetadata += QLatin1String( "<table class=\"list-view\">\n" );
// storage type
if ( !storageType().isEmpty() )
{
myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" ) + tr( "Storage" ) + QStringLiteral( "</td><td>" ) + storageType() + QStringLiteral( "</td></tr>\n" );
}
// comment
if ( !dataComment().isEmpty() )
{
myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" ) + tr( "Comment" ) + QStringLiteral( "</td><td>" ) + dataComment() + QStringLiteral( "</td></tr>\n" );
}
// encoding
if ( const QgsVectorDataProvider *provider = dataProvider() )
{
myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" ) + tr( "Encoding" ) + QStringLiteral( "</td><td>" ) + provider->encoding() + QStringLiteral( "</td></tr>\n" );
myMetadata += provider->htmlMetadata();
}
if ( isSpatial() )
{
// geom type
Qgis::GeometryType type = geometryType();
if ( static_cast<int>( type ) < 0 || static_cast< int >( type ) > static_cast< int >( Qgis::GeometryType::Null ) )
{
QgsDebugMsgLevel( QStringLiteral( "Invalid vector type" ), 2 );
}
else
{
QString typeString( QStringLiteral( "%1 (%2)" ).arg( QgsWkbTypes::geometryDisplayString( geometryType() ),
QgsWkbTypes::displayString( wkbType() ) ) );
myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" ) + tr( "Geometry type" ) + QStringLiteral( "</td><td>" ) + typeString + QStringLiteral( "</td></tr>\n" );
}
// geom column name
if ( const QgsVectorDataProvider *provider = dataProvider(); !provider->geometryColumnName().isEmpty() )
{
myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" ) + tr( "Geometry column" ) + QStringLiteral( "</td><td>" ) + provider->geometryColumnName() + QStringLiteral( "</td></tr>\n" );
}
// Extent
// Try to display extent 3D by default. If empty (probably because the data is 2D), fallback to the 2D version
const QgsBox3D extentBox3D = extent3D();
const QString extentAsStr = !extentBox3D.isEmpty() ? extentBox3D.toString() : extent().toString();
myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" ) + tr( "Extent" ) + QStringLiteral( "</td><td>" ) + extentAsStr + QStringLiteral( "</td></tr>\n" );
}
// feature count
QLocale locale = QLocale();
locale.setNumberOptions( locale.numberOptions() &= ~QLocale::NumberOption::OmitGroupSeparator );
myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" )
+ tr( "Feature count" ) + QStringLiteral( "</td><td>" )
+ ( featureCount() == -1 ? tr( "unknown" ) : locale.toString( static_cast<qlonglong>( featureCount() ) ) )
+ QStringLiteral( "</td></tr>\n" );
// End Provider section
myMetadata += QLatin1String( "</table>\n<br><br>" );
if ( isSpatial() )
{
// CRS
myMetadata += crsHtmlMetadata();
}
// identification section
myMetadata += QStringLiteral( "<h1>" ) + tr( "Identification" ) + QStringLiteral( "</h1>\n<hr>\n" );
myMetadata += htmlFormatter.identificationSectionHtml( );
myMetadata += QLatin1String( "<br><br>\n" );
// extent section
myMetadata += QStringLiteral( "<h1>" ) + tr( "Extent" ) + QStringLiteral( "</h1>\n<hr>\n" );
myMetadata += htmlFormatter.extentSectionHtml( isSpatial() );
myMetadata += QLatin1String( "<br><br>\n" );
// Start the Access section
myMetadata += QStringLiteral( "<h1>" ) + tr( "Access" ) + QStringLiteral( "</h1>\n<hr>\n" );
myMetadata += htmlFormatter.accessSectionHtml( );
myMetadata += QLatin1String( "<br><br>\n" );
// Fields section
myMetadata += QStringLiteral( "<h1>" ) + tr( "Fields" ) + QStringLiteral( "</h1>\n<hr>\n<table class=\"list-view\">\n" );
// primary key
QgsAttributeList pkAttrList = primaryKeyAttributes();
if ( !pkAttrList.isEmpty() )
{
myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" ) + tr( "Primary key attributes" ) + QStringLiteral( "</td><td>" );
const auto constPkAttrList = pkAttrList;
for ( int idx : constPkAttrList )
{
myMetadata += fields().at( idx ).name() + ' ';
}
myMetadata += QLatin1String( "</td></tr>\n" );
}
const QgsFields myFields = fields();
// count fields
myMetadata += QStringLiteral( "<tr><td class=\"highlight\">" ) + tr( "Count" ) + QStringLiteral( "</td><td>" ) + QString::number( myFields.size() ) + QStringLiteral( "</td></tr>\n" );
myMetadata += QLatin1String( "</table>\n<br><table width=\"100%\" class=\"tabular-view\">\n" );
myMetadata += QLatin1String( "<tr><th>" ) + tr( "Field" ) + QLatin1String( "</th><th>" ) + tr( "Type" ) + QLatin1String( "</th><th>" ) + tr( "Length" ) + QLatin1String( "</th><th>" ) + tr( "Precision" ) + QLatin1String( "</th><th>" ) + tr( "Comment" ) + QLatin1String( "</th></tr>\n" );
for ( int i = 0; i < myFields.size(); ++i )
{
QgsField myField = myFields.at( i );
QString rowClass;
if ( i % 2 )
rowClass = QStringLiteral( "class=\"odd-row\"" );
myMetadata += QLatin1String( "<tr " ) + rowClass + QLatin1String( "><td>" ) + myField.displayNameWithAlias() + QLatin1String( "</td><td>" ) + myField.typeName() + QLatin1String( "</td><td>" ) + QString::number( myField.length() ) + QLatin1String( "</td><td>" ) + QString::number( myField.precision() ) + QLatin1String( "</td><td>" ) + myField.comment() + QLatin1String( "</td></tr>\n" );
}
//close field list
myMetadata += QLatin1String( "</table>\n<br><br>" );
// Start the contacts section
myMetadata += QStringLiteral( "<h1>" ) + tr( "Contacts" ) + QStringLiteral( "</h1>\n<hr>\n" );
myMetadata += htmlFormatter.contactsSectionHtml( );
myMetadata += QLatin1String( "<br><br>\n" );
// Start the links section
myMetadata += QStringLiteral( "<h1>" ) + tr( "Links" ) + QStringLiteral( "</h1>\n<hr>\n" );
myMetadata += htmlFormatter.linksSectionHtml( );
myMetadata += QLatin1String( "<br><br>\n" );
// Start the history section
myMetadata += QStringLiteral( "<h1>" ) + tr( "History" ) + QStringLiteral( "</h1>\n<hr>\n" );
myMetadata += htmlFormatter.historySectionHtml( );
myMetadata += QLatin1String( "<br><br>\n" );
myMetadata += customPropertyHtmlMetadata();
myMetadata += QLatin1String( "\n</body>\n</html>\n" );
return myMetadata;
}
void QgsVectorLayer::invalidateSymbolCountedFlag()
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
mSymbolFeatureCounted = false;
}
void QgsVectorLayer::onFeatureCounterCompleted()
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
onSymbolsCounted();
mFeatureCounter = nullptr;
}
void QgsVectorLayer::onFeatureCounterTerminated()
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
mFeatureCounter = nullptr;
}
void QgsVectorLayer::onJoinedFieldsChanged()
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
// some of the fields of joined layers have changed -> we need to update this layer's fields too
updateFields();
}
void QgsVectorLayer::onFeatureAdded( QgsFeatureId fid )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
updateExtents();
emit featureAdded( fid );
}
void QgsVectorLayer::onFeatureDeleted( QgsFeatureId fid )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
updateExtents();
if ( mEditCommandActive || mCommitChangesActive )
{
mDeletedFids << fid;
}
else
{
mSelectedFeatureIds.remove( fid );
emit featuresDeleted( QgsFeatureIds() << fid );
}
emit featureDeleted( fid );
}
void QgsVectorLayer::onRelationsLoaded()
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
mEditFormConfig.onRelationsLoaded();
}
void QgsVectorLayer::onSymbolsCounted()
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( mFeatureCounter )
{
mSymbolFeatureCounted = true;
mSymbolFeatureCountMap = mFeatureCounter->symbolFeatureCountMap();
mSymbolFeatureIdMap = mFeatureCounter->symbolFeatureIdMap();
emit symbolFeatureCountMapChanged();
}
}
QList<QgsRelation> QgsVectorLayer::referencingRelations( int idx ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( QgsProject *p = project() )
return p->relationManager()->referencingRelations( this, idx );
else
return {};
}
QList<QgsWeakRelation> QgsVectorLayer::weakRelations() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return mWeakRelations;
}
void QgsVectorLayer::setWeakRelations( const QList<QgsWeakRelation> &relations )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
mWeakRelations = relations;
}
bool QgsVectorLayer::loadAuxiliaryLayer( const QgsAuxiliaryStorage &storage, const QString &key )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
bool rc = false;
QString joinKey = mAuxiliaryLayerKey;
if ( !key.isEmpty() )
joinKey = key;
if ( storage.isValid() && !joinKey.isEmpty() )
{
QgsAuxiliaryLayer *alayer = nullptr;
int idx = fields().lookupField( joinKey );
if ( idx >= 0 )
{
alayer = storage.createAuxiliaryLayer( fields().field( idx ), this );
if ( alayer )
{
setAuxiliaryLayer( alayer );
rc = true;
}
}
}
return rc;
}
void QgsVectorLayer::setAuxiliaryLayer( QgsAuxiliaryLayer *alayer )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
mAuxiliaryLayerKey.clear();
if ( mAuxiliaryLayer )
removeJoin( mAuxiliaryLayer->id() );
if ( alayer )
{
addJoin( alayer->joinInfo() );
if ( !alayer->isEditable() )
alayer->startEditing();
mAuxiliaryLayerKey = alayer->joinInfo().targetFieldName();
}
mAuxiliaryLayer.reset( alayer );
if ( mAuxiliaryLayer )
mAuxiliaryLayer->setParent( this );
updateFields();
}
const QgsAuxiliaryLayer *QgsVectorLayer::auxiliaryLayer() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return mAuxiliaryLayer.get();
}
QgsAuxiliaryLayer *QgsVectorLayer::auxiliaryLayer()
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return mAuxiliaryLayer.get();
}
QSet<QgsMapLayerDependency> QgsVectorLayer::dependencies() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( mDataProvider )
return mDataProvider->dependencies() + mDependencies;
return mDependencies;
}
void QgsVectorLayer::emitDataChanged()
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( mDataChangedFired )
return;
// If we are asked to fire dataChanged from a layer we depend on,
// be sure that this layer is not in the process of committing its changes, because
// we will be asked to fire dataChanged at the end of his commit, and we don't
// want to fire this signal more than necessary.
if ( QgsVectorLayer *layerWeDependUpon = qobject_cast<QgsVectorLayer *>( sender() );
layerWeDependUpon && layerWeDependUpon->mCommitChangesActive )
return;
updateExtents(); // reset cached extent to reflect data changes
mDataChangedFired = true;
emit dataChanged();
mDataChangedFired = false;
}
bool QgsVectorLayer::setDependencies( const QSet<QgsMapLayerDependency> &oDeps )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QSet<QgsMapLayerDependency> deps;
const auto constODeps = oDeps;
for ( const QgsMapLayerDependency &dep : constODeps )
{
if ( dep.origin() == QgsMapLayerDependency::FromUser )
deps << dep;
}
QSet<QgsMapLayerDependency> toAdd = deps - dependencies();
// disconnect layers that are not present in the list of dependencies anymore
if ( QgsProject *p = project() )
{
for ( const QgsMapLayerDependency &dep : std::as_const( mDependencies ) )
{
QgsVectorLayer *lyr = static_cast<QgsVectorLayer *>( p->mapLayer( dep.layerId() ) );
if ( !lyr )
continue;
disconnect( lyr, &QgsVectorLayer::featureAdded, this, &QgsVectorLayer::emitDataChanged );
disconnect( lyr, &QgsVectorLayer::featureDeleted, this, &QgsVectorLayer::emitDataChanged );
disconnect( lyr, &QgsVectorLayer::geometryChanged, this, &QgsVectorLayer::emitDataChanged );
disconnect( lyr, &QgsVectorLayer::dataChanged, this, &QgsVectorLayer::emitDataChanged );
disconnect( lyr, &QgsVectorLayer::repaintRequested, this, &QgsVectorLayer::triggerRepaint );
disconnect( lyr, &QgsVectorLayer::afterCommitChanges, this, &QgsVectorLayer::emitDataChanged );
}
}
// assign new dependencies
if ( mDataProvider )
mDependencies = mDataProvider->dependencies() + deps;
else
mDependencies = deps;
emit dependenciesChanged();
// connect to new layers
if ( QgsProject *p = project() )
{
for ( const QgsMapLayerDependency &dep : std::as_const( mDependencies ) )
{
QgsVectorLayer *lyr = static_cast<QgsVectorLayer *>( p->mapLayer( dep.layerId() ) );
if ( !lyr )
continue;
connect( lyr, &QgsVectorLayer::featureAdded, this, &QgsVectorLayer::emitDataChanged );
connect( lyr, &QgsVectorLayer::featureDeleted, this, &QgsVectorLayer::emitDataChanged );
connect( lyr, &QgsVectorLayer::geometryChanged, this, &QgsVectorLayer::emitDataChanged );
connect( lyr, &QgsVectorLayer::dataChanged, this, &QgsVectorLayer::emitDataChanged );
connect( lyr, &QgsVectorLayer::repaintRequested, this, &QgsVectorLayer::triggerRepaint );
connect( lyr, &QgsVectorLayer::afterCommitChanges, this, &QgsVectorLayer::emitDataChanged );
}
}
// if new layers are present, emit a data change
if ( ! toAdd.isEmpty() )
emitDataChanged();
return true;
}
QgsFieldConstraints::Constraints QgsVectorLayer::fieldConstraints( int fieldIndex ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( fieldIndex < 0 || fieldIndex >= mFields.count() || !mDataProvider )
return QgsFieldConstraints::Constraints();
QgsFieldConstraints::Constraints constraints = mFields.at( fieldIndex ).constraints().constraints();
// make sure provider constraints are always present!
if ( mFields.fieldOrigin( fieldIndex ) == Qgis::FieldOrigin::Provider )
{
constraints |= mDataProvider->fieldConstraints( mFields.fieldOriginIndex( fieldIndex ) );
}
return constraints;
}
QMap< QgsFieldConstraints::Constraint, QgsFieldConstraints::ConstraintStrength> QgsVectorLayer::fieldConstraintsAndStrength( int fieldIndex ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QMap< QgsFieldConstraints::Constraint, QgsFieldConstraints::ConstraintStrength > m;
if ( fieldIndex < 0 || fieldIndex >= mFields.count() )
return m;
QString name = mFields.at( fieldIndex ).name();
QMap< QPair< QString, QgsFieldConstraints::Constraint >, QgsFieldConstraints::ConstraintStrength >::const_iterator conIt = mFieldConstraintStrength.constBegin();
for ( ; conIt != mFieldConstraintStrength.constEnd(); ++conIt )
{
if ( conIt.key().first == name )
{
m[ conIt.key().second ] = mFieldConstraintStrength.value( conIt.key() );
}
}
return m;
}
void QgsVectorLayer::setFieldConstraint( int index, QgsFieldConstraints::Constraint constraint, QgsFieldConstraints::ConstraintStrength strength )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( index < 0 || index >= mFields.count() )
return;
QString name = mFields.at( index ).name();
// add constraint to existing constraints
QgsFieldConstraints::Constraints constraints = mFieldConstraints.value( name, QgsFieldConstraints::Constraints() );
constraints |= constraint;
mFieldConstraints.insert( name, constraints );
mFieldConstraintStrength.insert( qMakePair( name, constraint ), strength );
updateFields();
}
void QgsVectorLayer::removeFieldConstraint( int index, QgsFieldConstraints::Constraint constraint )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( index < 0 || index >= mFields.count() )
return;
QString name = mFields.at( index ).name();
// remove constraint from existing constraints
QgsFieldConstraints::Constraints constraints = mFieldConstraints.value( name, QgsFieldConstraints::Constraints() );
constraints &= ~constraint;
mFieldConstraints.insert( name, constraints );
mFieldConstraintStrength.remove( qMakePair( name, constraint ) );
updateFields();
}
QString QgsVectorLayer::constraintExpression( int index ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( index < 0 || index >= mFields.count() )
return QString();
return mFields.at( index ).constraints().constraintExpression();
}
QString QgsVectorLayer::constraintDescription( int index ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( index < 0 || index >= mFields.count() )
return QString();
return mFields.at( index ).constraints().constraintDescription();
}
void QgsVectorLayer::setConstraintExpression( int index, const QString &expression, const QString &description )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( index < 0 || index >= mFields.count() )
return;
if ( expression.isEmpty() )
{
mFieldConstraintExpressions.remove( mFields.at( index ).name() );
}
else
{
mFieldConstraintExpressions.insert( mFields.at( index ).name(), qMakePair( expression, description ) );
}
updateFields();
}
void QgsVectorLayer::setFieldConfigurationFlags( int index, Qgis::FieldConfigurationFlags flags )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( index < 0 || index >= mFields.count() )
return;
mFieldConfigurationFlags.insert( mFields.at( index ).name(), flags );
updateFields();
}
void QgsVectorLayer::setFieldConfigurationFlag( int index, Qgis::FieldConfigurationFlag flag, bool active )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( index < 0 || index >= mFields.count() )
return;
Qgis::FieldConfigurationFlags flags = mFields.at( index ).configurationFlags();
flags.setFlag( flag, active );
setFieldConfigurationFlags( index, flags );
}
Qgis::FieldConfigurationFlags QgsVectorLayer::fieldConfigurationFlags( int index ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( index < 0 || index >= mFields.count() )
return Qgis::FieldConfigurationFlag::NoFlag;
return mFields.at( index ).configurationFlags();
}
void QgsVectorLayer::setEditorWidgetSetup( int index, const QgsEditorWidgetSetup &setup )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( index < 0 || index >= mFields.count() )
return;
if ( setup.isNull() )
mFieldWidgetSetups.remove( mFields.at( index ).name() );
else
mFieldWidgetSetups.insert( mFields.at( index ).name(), setup );
updateFields();
}
QgsEditorWidgetSetup QgsVectorLayer::editorWidgetSetup( int index ) const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( index < 0 || index >= mFields.count() )
return QgsEditorWidgetSetup();
return mFields.at( index ).editorWidgetSetup();
}
QgsAbstractVectorLayerLabeling *QgsVectorLayer::readLabelingFromCustomProperties()
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QgsAbstractVectorLayerLabeling *labeling = nullptr;
if ( customProperty( QStringLiteral( "labeling" ) ).toString() == QLatin1String( "pal" ) )
{
if ( customProperty( QStringLiteral( "labeling/enabled" ), QVariant( false ) ).toBool() )
{
// try to load from custom properties
QgsPalLayerSettings settings;
settings.readFromLayerCustomProperties( this );
labeling = new QgsVectorLayerSimpleLabeling( settings );
}
// also clear old-style labeling config
removeCustomProperty( QStringLiteral( "labeling" ) );
const auto constCustomPropertyKeys = customPropertyKeys();
for ( const QString &key : constCustomPropertyKeys )
{
if ( key.startsWith( QLatin1String( "labeling/" ) ) )
removeCustomProperty( key );
}
}
return labeling;
}
bool QgsVectorLayer::allowCommit() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return mAllowCommit;
}
void QgsVectorLayer::setAllowCommit( bool allowCommit )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
if ( mAllowCommit == allowCommit )
return;
mAllowCommit = allowCommit;
emit allowCommitChanged();
}
QgsGeometryOptions *QgsVectorLayer::geometryOptions() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return mGeometryOptions.get();
}
void QgsVectorLayer::setReadExtentFromXml( bool readExtentFromXml )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
mReadExtentFromXml = readExtentFromXml;
}
bool QgsVectorLayer::readExtentFromXml() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
return mReadExtentFromXml;
}
void QgsVectorLayer::onDirtyTransaction( const QString &sql, const QString &name )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QgsTransaction *tr = dataProvider()->transaction();
if ( tr && mEditBuffer )
{
qobject_cast<QgsVectorLayerEditPassthrough *>( mEditBuffer )->update( tr, sql, name );
}
}
QList<QgsVectorLayer *> QgsVectorLayer::DeleteContext::handledLayers( bool includeAuxiliaryLayers ) const
{
QList<QgsVectorLayer *> layers;
QMap<QgsVectorLayer *, QgsFeatureIds>::const_iterator i;
for ( i = mHandledFeatures.begin(); i != mHandledFeatures.end(); ++i )
{
if ( includeAuxiliaryLayers || !qobject_cast< QgsAuxiliaryLayer * >( i.key() ) )
layers.append( i.key() );
}
return layers;
}
QgsFeatureIds QgsVectorLayer::DeleteContext::handledFeatures( QgsVectorLayer *layer ) const
{
return mHandledFeatures[layer];
}