QGIS/src/core/qgsvectorlayereditbuffer.cpp
Nyall Dawson 24a9c522e3 Move qgsAsConst to qgis::as_const
So it's more inline with the std::as_const implementation which
it fills in for, and allows us to 'polyfill' other c++>11
features into the qgis:: namespace.
2017-10-13 19:47:37 +10:00

787 lines
24 KiB
C++

/***************************************************************************
qgsvectorlayereditbuffer.cpp
---------------------
begin : Dezember 2012
copyright : (C) 2012 by Martin Dobias
email : wonder dot sk at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsvectorlayereditbuffer.h"
#include "qgsgeometry.h"
#include "qgslogger.h"
#include "qgsvectorlayerundocommand.h"
#include "qgsvectordataprovider.h"
#include "qgsvectorlayer.h"
//! populate two lists (ks, vs) from map - in reverse order
template <class Key, class T> void mapToReversedLists( const QMap< Key, T > &map, QList<Key> &ks, QList<T> &vs )
{
ks.reserve( map.size() );
vs.reserve( map.size() );
typename QMap<Key, T>::const_iterator i = map.constEnd();
while ( i-- != map.constBegin() )
{
ks.append( i.key() );
vs.append( i.value() );
}
}
QgsVectorLayerEditBuffer::QgsVectorLayerEditBuffer( QgsVectorLayer *layer )
: L( layer )
{
connect( L->undoStack(), &QUndoStack::indexChanged, this, &QgsVectorLayerEditBuffer::undoIndexChanged ); // TODO[MD]: queued?
}
bool QgsVectorLayerEditBuffer::isModified() const
{
return !L->undoStack()->isClean();
}
void QgsVectorLayerEditBuffer::undoIndexChanged( int index )
{
QgsDebugMsgLevel( QString( "undo index changed %1" ).arg( index ), 4 );
Q_UNUSED( index );
emit layerModified();
}
void QgsVectorLayerEditBuffer::updateFields( QgsFields &fields )
{
// delete attributes from the higher indices to lower indices
for ( int i = mDeletedAttributeIds.count() - 1; i >= 0; --i )
{
fields.remove( mDeletedAttributeIds.at( i ) );
}
// add new fields
for ( int i = 0; i < mAddedAttributes.count(); ++i )
{
fields.append( mAddedAttributes.at( i ), QgsFields::OriginEdit, i );
}
// rename fields
QgsFieldNameMap::const_iterator renameIt = mRenamedAttributes.constBegin();
for ( ; renameIt != mRenamedAttributes.constEnd(); ++renameIt )
{
fields[ renameIt.key()].setName( renameIt.value() );
}
}
void QgsVectorLayerEditBuffer::updateFeatureGeometry( QgsFeature &f )
{
if ( mChangedGeometries.contains( f.id() ) )
f.setGeometry( mChangedGeometries[f.id()] );
}
void QgsVectorLayerEditBuffer::updateChangedAttributes( QgsFeature &f )
{
QgsAttributes attrs = f.attributes();
// remove all attributes that will disappear - from higher indices to lower
for ( int idx = mDeletedAttributeIds.count() - 1; idx >= 0; --idx )
{
attrs.remove( mDeletedAttributeIds[idx] );
}
// adjust size to accommodate added attributes
attrs.resize( attrs.count() + mAddedAttributes.count() );
// update changed attributes
if ( mChangedAttributeValues.contains( f.id() ) )
{
const QgsAttributeMap &map = mChangedAttributeValues[f.id()];
for ( QgsAttributeMap::const_iterator it = map.begin(); it != map.end(); ++it )
attrs[it.key()] = it.value();
}
f.setAttributes( attrs );
}
bool QgsVectorLayerEditBuffer::addFeature( QgsFeature &f )
{
if ( !( L->dataProvider()->capabilities() & QgsVectorDataProvider::AddFeatures ) )
{
return false;
}
if ( L->mFields.count() != f.attributes().count() )
return false;
// TODO: check correct geometry type
L->undoStack()->push( new QgsVectorLayerUndoCommandAddFeature( this, f ) );
return true;
}
bool QgsVectorLayerEditBuffer::addFeatures( QgsFeatureList &features )
{
if ( !( L->dataProvider()->capabilities() & QgsVectorDataProvider::AddFeatures ) )
return false;
bool result = true;
for ( QgsFeatureList::iterator iter = features.begin(); iter != features.end(); ++iter )
{
result = result && addFeature( *iter );
}
L->updateExtents();
return result;
}
bool QgsVectorLayerEditBuffer::deleteFeature( QgsFeatureId fid )
{
if ( !( L->dataProvider()->capabilities() & QgsVectorDataProvider::DeleteFeatures ) )
{
QgsDebugMsg( "Cannot delete features (missing DeleteFeature capability)" );
return false;
}
if ( FID_IS_NEW( fid ) )
{
if ( !mAddedFeatures.contains( fid ) )
{
QgsDebugMsg( "Cannot delete features (in the list of added features)" );
return false;
}
}
else // existing feature
{
if ( mDeletedFeatureIds.contains( fid ) )
{
QgsDebugMsg( "Cannot delete features (in the list of deleted features)" );
return false;
}
}
L->undoStack()->push( new QgsVectorLayerUndoCommandDeleteFeature( this, fid ) );
return true;
}
bool QgsVectorLayerEditBuffer::deleteFeatures( const QgsFeatureIds &fids )
{
if ( !( L->dataProvider()->capabilities() & QgsVectorDataProvider::DeleteFeatures ) )
{
QgsDebugMsg( "Cannot delete features (missing DeleteFeatures capability)" );
return false;
}
bool ok = true;
Q_FOREACH ( QgsFeatureId fid, fids )
ok = deleteFeature( fid ) && ok;
return ok;
}
bool QgsVectorLayerEditBuffer::changeGeometry( QgsFeatureId fid, const QgsGeometry &geom )
{
if ( !L->isSpatial() )
{
return false;
}
if ( FID_IS_NEW( fid ) )
{
if ( !mAddedFeatures.contains( fid ) )
return false;
}
else if ( !( L->dataProvider()->capabilities() & QgsVectorDataProvider::ChangeGeometries ) )
return false;
// TODO: check compatible geometry
L->undoStack()->push( new QgsVectorLayerUndoCommandChangeGeometry( this, fid, geom ) );
return true;
}
bool QgsVectorLayerEditBuffer::changeAttributeValue( QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue )
{
if ( FID_IS_NEW( fid ) )
{
if ( !mAddedFeatures.contains( fid ) )
return false;
}
else if ( !( L->dataProvider()->capabilities() & QgsVectorDataProvider::ChangeAttributeValues ) )
{
return false;
}
if ( field < 0 || field >= L->fields().count() ||
L->fields().fieldOrigin( field ) == QgsFields::OriginJoin ||
L->fields().fieldOrigin( field ) == QgsFields::OriginExpression )
return false;
L->undoStack()->push( new QgsVectorLayerUndoCommandChangeAttribute( this, fid, field, newValue, oldValue ) );
return true;
}
bool QgsVectorLayerEditBuffer::addAttribute( const QgsField &field )
{
if ( !( L->dataProvider()->capabilities() & QgsVectorDataProvider::AddAttributes ) )
return false;
if ( field.name().isEmpty() )
return false;
Q_FOREACH ( const QgsField &updatedField, L->fields() )
{
if ( updatedField.name() == field.name() )
return false;
}
if ( !L->dataProvider()->supportedType( field ) )
return false;
L->undoStack()->push( new QgsVectorLayerUndoCommandAddAttribute( this, field ) );
return true;
}
bool QgsVectorLayerEditBuffer::deleteAttribute( int index )
{
if ( !( L->dataProvider()->capabilities() & QgsVectorDataProvider::DeleteAttributes ) )
return false;
if ( index < 0 || index >= L->fields().count() )
return false;
// find out source of the field
QgsFields::FieldOrigin origin = L->fields().fieldOrigin( index );
int originIndex = L->fields().fieldOriginIndex( index );
if ( origin == QgsFields::OriginProvider && mDeletedAttributeIds.contains( originIndex ) )
return false;
if ( origin == QgsFields::OriginJoin )
return false;
L->undoStack()->push( new QgsVectorLayerUndoCommandDeleteAttribute( this, index ) );
return true;
}
bool QgsVectorLayerEditBuffer::renameAttribute( int index, const QString &newName )
{
if ( !( L->dataProvider()->capabilities() & QgsVectorDataProvider::RenameAttributes ) )
return false;
if ( newName.isEmpty() )
return false;
if ( index < 0 || index >= L->fields().count() )
return false;
Q_FOREACH ( const QgsField &updatedField, L->fields() )
{
if ( updatedField.name() == newName )
return false;
}
L->undoStack()->push( new QgsVectorLayerUndoCommandRenameAttribute( this, index, newName ) );
return true;
}
bool QgsVectorLayerEditBuffer::commitChanges( QStringList &commitErrors )
{
QgsVectorDataProvider *provider = L->dataProvider();
commitErrors.clear();
int cap = provider->capabilities();
bool success = true;
// geometry updates attribute updates
// yes no => changeGeometryValues
// no yes => changeAttributeValues
// yes yes => changeFeatures
// to fix https://issues.qgis.org/issues/15741
// first of all check if feature to add is compatible with provider type
// this check have to be done before all checks to avoid to clear internal
// buffer if some of next steps success.
if ( success && !mAddedFeatures.isEmpty() )
{
if ( cap & QgsVectorDataProvider::AddFeatures )
{
if ( provider->doesStrictFeatureTypeCheck() )
{
for ( const auto &f : qgis::as_const( mAddedFeatures ) )
{
if ( ( ! f.hasGeometry() ) ||
( f.geometry().wkbType() == provider->wkbType() ) )
continue;
if ( provider->convertToProviderType( f.geometry() ).isNull() )
{
commitErrors << tr( "ERROR: %n feature(s) not added - geometry type is not compatible with the current layer.", "not added features count", mAddedFeatures.size() );
success = false;
break;
}
}
}
}
else
{
commitErrors << tr( "ERROR: %n feature(s) not added - provider doesn't support adding features.", "not added features count", mAddedFeatures.size() );
success = false;
}
}
//
// update geometries
//
if ( !mChangedGeometries.isEmpty() && ( ( cap & QgsVectorDataProvider::ChangeFeatures ) == 0 || mChangedAttributeValues.isEmpty() ) )
{
if ( provider->changeGeometryValues( mChangedGeometries ) )
{
commitErrors << tr( "SUCCESS: %n geometries were changed.", "changed geometries count", mChangedGeometries.size() );
emit committedGeometriesChanges( L->id(), mChangedGeometries );
mChangedGeometries.clear();
}
else
{
commitErrors << tr( "ERROR: %n geometries not changed.", "not changed geometries count", mChangedGeometries.size() );
success = false;
}
}
QgsFields oldFields = L->fields();
//
// delete attributes
//
bool attributesChanged = false;
if ( !mDeletedAttributeIds.isEmpty() )
{
if ( ( cap & QgsVectorDataProvider::DeleteAttributes ) && provider->deleteAttributes( mDeletedAttributeIds.toSet() ) )
{
commitErrors << tr( "SUCCESS: %n attribute(s) deleted.", "deleted attributes count", mDeletedAttributeIds.size() );
emit committedAttributesDeleted( L->id(), mDeletedAttributeIds );
mDeletedAttributeIds.clear();
attributesChanged = true;
}
else
{
commitErrors << tr( "ERROR: %n attribute(s) not deleted.", "not deleted attributes count", mDeletedAttributeIds.size() );
#if 0
QString list = "ERROR: Pending attribute deletes:";
Q_FOREACH ( int idx, mDeletedAttributeIds )
{
list.append( ' ' + L->pendingFields().at( idx ).name() );
}
commitErrors << list;
#endif
success = false;
}
}
//
// add attributes
//
if ( !mAddedAttributes.isEmpty() )
{
if ( ( cap & QgsVectorDataProvider::AddAttributes ) && provider->addAttributes( mAddedAttributes ) )
{
commitErrors << tr( "SUCCESS: %n attribute(s) added.", "added attributes count", mAddedAttributes.size() );
emit committedAttributesAdded( L->id(), mAddedAttributes );
mAddedAttributes.clear();
attributesChanged = true;
}
else
{
commitErrors << tr( "ERROR: %n new attribute(s) not added", "not added attributes count", mAddedAttributes.size() );
#if 0
QString list = "ERROR: Pending adds:";
Q_FOREACH ( QgsField f, mAddedAttributes )
{
list.append( ' ' + f.name() );
}
commitErrors << list;
#endif
success = false;
}
}
// rename attributes
if ( !mRenamedAttributes.isEmpty() )
{
if ( ( cap & QgsVectorDataProvider::RenameAttributes ) && provider->renameAttributes( mRenamedAttributes ) )
{
commitErrors << tr( "SUCCESS: %n attribute(s) renamed.", "renamed attributes count", mRenamedAttributes.size() );
emit committedAttributesRenamed( L->id(), mRenamedAttributes );
mRenamedAttributes.clear();
attributesChanged = true;
}
else
{
commitErrors << tr( "ERROR: %n attribute(s) not renamed", "not renamed attributes count", mRenamedAttributes.size() );
success = false;
}
}
//
// check that addition/removal went as expected
//
bool attributeChangesOk = true;
if ( attributesChanged )
{
L->updateFields();
QgsFields newFields = L->fields();
if ( oldFields.count() != newFields.count() )
{
commitErrors << tr( "ERROR: the count of fields is incorrect after addition/removal of fields!" );
attributeChangesOk = false; // don't try attribute updates - they'll fail.
}
for ( int i = 0; i < std::min( oldFields.count(), newFields.count() ); ++i )
{
QgsField oldField = oldFields.at( i );
QgsField newField = newFields.at( i );
if ( attributeChangesOk && oldField != newField )
{
commitErrors
<< tr( "ERROR: field with index %1 is not the same!" ).arg( i )
<< tr( "Provider: %1" ).arg( L->providerType() )
<< tr( "Storage: %1" ).arg( L->storageType() )
<< QStringLiteral( "%1: name=%2 type=%3 typeName=%4 len=%5 precision=%6" )
.arg( tr( "expected field" ),
oldField.name(),
QVariant::typeToName( oldField.type() ),
oldField.typeName() )
.arg( oldField.length() )
.arg( oldField.precision() )
<< QStringLiteral( "%1: name=%2 type=%3 typeName=%4 len=%5 precision=%6" )
.arg( tr( "retrieved field" ),
newField.name(),
QVariant::typeToName( newField.type() ),
newField.typeName() )
.arg( newField.length() )
.arg( newField.precision() );
attributeChangesOk = false; // don't try attribute updates - they'll fail.
}
}
}
if ( attributeChangesOk )
{
if ( cap & QgsVectorDataProvider::ChangeFeatures && !mChangedGeometries.isEmpty() && !mChangedAttributeValues.isEmpty() )
{
Q_ASSERT( ( cap & ( QgsVectorDataProvider::ChangeAttributeValues | QgsVectorDataProvider::ChangeGeometries ) ) == ( QgsVectorDataProvider::ChangeAttributeValues | QgsVectorDataProvider::ChangeGeometries ) );
if ( provider->changeFeatures( mChangedAttributeValues, mChangedGeometries ) )
{
commitErrors << tr( "SUCCESS: %1 attribute value(s) and %2 geometries changed." ).arg( mChangedAttributeValues.size(), mChangedGeometries.size() );
emit committedAttributeValuesChanges( L->id(), mChangedAttributeValues );
mChangedAttributeValues.clear();
emit committedGeometriesChanges( L->id(), mChangedGeometries );
mChangedGeometries.clear();
}
else
{
success = false;
}
}
else
{
//
// change attributes
//
if ( !mChangedAttributeValues.isEmpty() && ( ( cap & QgsVectorDataProvider::ChangeFeatures ) == 0 || mChangedGeometries.isEmpty() ) )
{
if ( ( cap & QgsVectorDataProvider::ChangeAttributeValues ) && provider->changeAttributeValues( mChangedAttributeValues ) )
{
commitErrors << tr( "SUCCESS: %n attribute value(s) changed.", "changed attribute values count", mChangedAttributeValues.size() );
emit committedAttributeValuesChanges( L->id(), mChangedAttributeValues );
mChangedAttributeValues.clear();
}
else
{
commitErrors << tr( "ERROR: %n attribute value change(s) not applied.", "not changed attribute values count", mChangedAttributeValues.size() );
#if 0
QString list = "ERROR: pending changes:";
Q_FOREACH ( QgsFeatureId id, mChangedAttributeValues.keys() )
{
list.append( "\n " + FID_TO_STRING( id ) + '[' );
Q_FOREACH ( int idx, mChangedAttributeValues[ id ].keys() )
{
list.append( QString( " %1:%2" ).arg( L->pendingFields().at( idx ).name() ).arg( mChangedAttributeValues[id][idx].toString() ) );
}
list.append( " ]" );
}
commitErrors << list;
#endif
success = false;
}
}
}
//
// delete features
//
if ( success && !mDeletedFeatureIds.isEmpty() )
{
if ( ( cap & QgsVectorDataProvider::DeleteFeatures ) && provider->deleteFeatures( mDeletedFeatureIds ) )
{
commitErrors << tr( "SUCCESS: %n feature(s) deleted.", "deleted features count", mDeletedFeatureIds.size() );
// TODO[MD]: we should not need this here
Q_FOREACH ( QgsFeatureId id, mDeletedFeatureIds )
{
mChangedAttributeValues.remove( id );
mChangedGeometries.remove( id );
}
emit committedFeaturesRemoved( L->id(), mDeletedFeatureIds );
mDeletedFeatureIds.clear();
}
else
{
commitErrors << tr( "ERROR: %n feature(s) not deleted.", "not deleted features count", mDeletedFeatureIds.size() );
#if 0
QString list = "ERROR: pending deletes:";
Q_FOREACH ( QgsFeatureId id, mDeletedFeatureIds )
{
list.append( ' ' + FID_TO_STRING( id ) );
}
commitErrors << list;
#endif
success = false;
}
}
//
// add features
//
if ( success && !mAddedFeatures.isEmpty() )
{
if ( cap & QgsVectorDataProvider::AddFeatures )
{
QList<QgsFeatureId> ids;
QgsFeatureList featuresToAdd;
// get the list of added features in reversed order
// this will preserve the order how they have been added e.g. (-1, -2, -3) while in the map they are ordered (-3, -2, -1)
mapToReversedLists( mAddedFeatures, ids, featuresToAdd );
if ( provider->addFeatures( featuresToAdd ) )
{
commitErrors << tr( "SUCCESS: %n feature(s) added.", "added features count", featuresToAdd.size() );
emit committedFeaturesAdded( L->id(), featuresToAdd );
// notify everyone that the features with temporary ids were updated with permanent ids
for ( int i = 0; i < featuresToAdd.count(); ++i )
{
if ( featuresToAdd[i].id() != ids[i] )
{
//update selection
if ( L->mSelectedFeatureIds.contains( ids[i] ) )
{
L->mSelectedFeatureIds.remove( ids[i] );
L->mSelectedFeatureIds.insert( featuresToAdd[i].id() );
}
emit featureDeleted( ids[i] );
emit featureAdded( featuresToAdd[i].id() );
}
}
mAddedFeatures.clear();
}
else
{
commitErrors << tr( "ERROR: %n feature(s) not added.", "not added features count", mAddedFeatures.size() );
#if 0
QString list = "ERROR: pending adds:";
Q_FOREACH ( QgsFeature f, mAddedFeatures )
{
list.append( ' ' + FID_TO_STRING( f.id() ) + '[' );
for ( int i = 0; i < L->pendingFields().size(); i++ )
{
list.append( QString( " %1:%2" ).arg( L->pendingFields().at( i ).name() ).arg( f.attributes()[i].toString() ) );
}
list.append( " ]" );
}
commitErrors << list;
#endif
success = false;
}
}
else
{
commitErrors << tr( "ERROR: %n feature(s) not added - provider doesn't support adding features.", "not added features count", mAddedFeatures.size() );
success = false;
}
}
}
else
{
success = false;
}
if ( !success && provider->hasErrors() )
{
commitErrors << tr( "\n Provider errors:" );
Q_FOREACH ( QString e, provider->errors() )
{
commitErrors << " " + e.replace( '\n', QLatin1String( "\n " ) );
}
provider->clearErrors();
}
return success;
}
void QgsVectorLayerEditBuffer::rollBack()
{
if ( !isModified() )
return;
// limit canvas redraws to one by jumping to beginning of stack
// see QgsUndoWidget::indexChanged
L->undoStack()->setIndex( 0 );
Q_ASSERT( mAddedAttributes.isEmpty() );
Q_ASSERT( mDeletedAttributeIds.isEmpty() );
Q_ASSERT( mChangedAttributeValues.isEmpty() );
Q_ASSERT( mChangedGeometries.isEmpty() );
Q_ASSERT( mAddedFeatures.isEmpty() );
}
#if 0
QString QgsVectorLayerEditBuffer::dumpEditBuffer()
{
QString msg;
if ( !mChangedGeometries.isEmpty() )
{
msg += "CHANGED GEOMETRIES:\n";
for ( QgsGeometryMap::const_iterator it = mChangedGeometries.begin(); it != mChangedGeometries.end(); ++it )
{
// QgsFeatureId, QgsGeometry
msg += QString( "- FID %1: %2" ).arg( it.key() ).arg( it.value().to );
}
}
return msg;
}
#endif
void QgsVectorLayerEditBuffer::handleAttributeAdded( int index )
{
// go through the changed attributes map and adapt indices
QgsChangedAttributesMap::iterator it = mChangedAttributeValues.begin();
for ( ; it != mChangedAttributeValues.end(); ++it )
{
updateAttributeMapIndex( it.value(), index, + 1 );
}
// go through added features and adapt attributes
QgsFeatureMap::iterator featureIt = mAddedFeatures.begin();
for ( ; featureIt != mAddedFeatures.end(); ++featureIt )
{
QgsAttributes attrs = featureIt->attributes();
attrs.insert( index, QVariant() );
featureIt->setAttributes( attrs );
}
// go through renamed attributes and adapt
QList< int > sortedRenamedIndices = mRenamedAttributes.keys();
//sort keys
std::sort( sortedRenamedIndices.begin(), sortedRenamedIndices.end(), std::greater< int >() );
Q_FOREACH ( int renameIndex, sortedRenamedIndices )
{
if ( renameIndex >= index )
{
mRenamedAttributes[ renameIndex + 1 ] = mRenamedAttributes.value( renameIndex );
}
}
//remove last
mRenamedAttributes.remove( index );
}
void QgsVectorLayerEditBuffer::handleAttributeDeleted( int index )
{
// go through the changed attributes map and adapt indices
QgsChangedAttributesMap::iterator it = mChangedAttributeValues.begin();
for ( ; it != mChangedAttributeValues.end(); ++it )
{
QgsAttributeMap &attrMap = it.value();
// remove the attribute
if ( attrMap.contains( index ) )
attrMap.remove( index );
// update attribute indices
updateAttributeMapIndex( attrMap, index, -1 );
}
// go through added features and adapt attributes
QgsFeatureMap::iterator featureIt = mAddedFeatures.begin();
for ( ; featureIt != mAddedFeatures.end(); ++featureIt )
{
QgsAttributes attrs = featureIt->attributes();
attrs.remove( index );
featureIt->setAttributes( attrs );
}
// go through rename attributes and adapt
QList< int > sortedRenamedIndices = mRenamedAttributes.keys();
//sort keys
std::sort( sortedRenamedIndices.begin(), sortedRenamedIndices.end() );
int last = -1;
mRenamedAttributes.remove( index );
Q_FOREACH ( int renameIndex, sortedRenamedIndices )
{
if ( renameIndex > index )
{
mRenamedAttributes.insert( renameIndex - 1, mRenamedAttributes.value( renameIndex ) );
last = renameIndex;
}
}
//remove last
if ( last > -1 )
mRenamedAttributes.remove( last );
}
void QgsVectorLayerEditBuffer::updateAttributeMapIndex( QgsAttributeMap &map, int index, int offset ) const
{
QgsAttributeMap updatedMap;
for ( QgsAttributeMap::const_iterator it = map.constBegin(); it != map.constEnd(); ++it )
{
int attrIndex = it.key();
updatedMap.insert( attrIndex < index ? attrIndex : attrIndex + offset, it.value() );
}
map = updatedMap;
}
void QgsVectorLayerEditBuffer::updateLayerFields()
{
L->updateFields();
}