Skip to content

Commit

Permalink
Port remaining map tools, paste features, offline editing
Browse files Browse the repository at this point in the history
to use new constraint/default clause handling methods
  • Loading branch information
nyalldawson authored and m-kuhn committed Nov 16, 2016
1 parent 091f488 commit 631bd48
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 134 deletions.
62 changes: 18 additions & 44 deletions src/app/qgisapp.cpp
Expand Up @@ -241,6 +241,7 @@
#include "qgsvirtuallayerdefinitionutils.h"
#include "qgstransaction.h"
#include "qgstransactiongroup.h"
#include "qgsvectorlayerutils.h"

#include "qgssublayersdialog.h"
#include "ogr/qgsopenvectorlayerdialog.h"
Expand Down Expand Up @@ -7484,7 +7485,6 @@ void QgisApp::editPaste( QgsMapLayer *destinationLayer )

QHash<int, int> remap;
QgsFields fields = clipboard()->fields();
QgsAttributeList pkAttrList = pasteVectorLayer->pkAttributeList();
for ( int idx = 0; idx < fields.count(); ++idx )
{
int dst = pasteVectorLayer->fields().lookupField( fields.at( idx ).name() );
Expand All @@ -7495,55 +7495,25 @@ void QgisApp::editPaste( QgsMapLayer *destinationLayer )
}

QgsExpressionContext context = pasteVectorLayer->createExpressionContext();
int dstAttrCount = pasteVectorLayer->fields().count();

QgsFeatureList::iterator featureIt = features.begin();
while ( featureIt != features.end() )
QgsFeatureIds newIds;

QgsFeatureList::const_iterator featureIt = features.constBegin();
while ( featureIt != features.constEnd() )
{
QgsAttributes srcAttr = featureIt->attributes();
QgsAttributes dstAttr( dstAttrCount );

// pre-initialized with default values
for ( int dst = 0; dst < dstAttr.count(); ++dst )
{
QVariant defVal;
if ( !pasteVectorLayer->defaultValueExpression( dst ).isEmpty() )
{
// client side default expression set - use this in preference to provider default
defVal = pasteVectorLayer->defaultValue( dst, *featureIt, &context );
}
else
{
defVal = pasteVectorLayer->dataProvider()->defaultValueClause( dst );
}

if ( defVal.isValid() && !defVal.isNull() )
{
dstAttr[ dst ] = defVal;
}
}
QgsAttributeMap dstAttr;

for ( int src = 0; src < srcAttr.count(); ++src )
{
int dst = remap.value( src, -1 );
if ( dst < 0 )
continue;

// don't overwrite default value for primary key fields if it's NOT NULL
// or for spatialite layers
if ( pkAttrList.contains( dst ) )
{
if ( !dstAttr.at( dst ).isNull() )
continue;
else if ( pasteVectorLayer->providerType() == QLatin1String( "spatialite" ) )
continue;
}

dstAttr[ dst ] = srcAttr.at( src );
}

featureIt->setAttributes( dstAttr );

QgsGeometry geom = featureIt->geometry();
if ( featureIt->hasGeometry() )
{
// convert geometry to match destination layer
Expand All @@ -7558,25 +7528,29 @@ void QgisApp::editPaste( QgsMapLayer *destinationLayer )

if ( destType != QgsWkbTypes::UnknownGeometry )
{
QgsGeometry newGeometry = featureIt->geometry().convertToType( destType, destIsMulti );
QgsGeometry newGeometry = geom.convertToType( destType, destIsMulti );
if ( newGeometry.isEmpty() )
{
featureIt = features.erase( featureIt );
continue;
}
featureIt->setGeometry( newGeometry );
geom = newGeometry;
}
// avoid intersection if enabled in digitize settings
QgsGeometry g = featureIt->geometry();
g.avoidIntersections();
featureIt->setGeometry( g );
geom.avoidIntersections();
}

// now create new feature using pasted feature as a template. This automatically handles default
// values and field constraints
QgsFeature newFeature = QgsVectorLayerUtils::createFeature( pasteVectorLayer, geom, dstAttr, &context );
pasteVectorLayer->addFeature( newFeature, false );
newIds << newFeature.id();

++featureIt;
}

pasteVectorLayer->addFeatures( features );
pasteVectorLayer->selectByIds( newIds );
pasteVectorLayer->endEditCommand();
pasteVectorLayer->updateExtents();

int nCopiedFeatures = features.count();
if ( nCopiedFeatures == 0 )
Expand Down
44 changes: 13 additions & 31 deletions src/app/qgsfeatureaction.cpp
Expand Up @@ -28,6 +28,7 @@
#include "qgsvectorlayer.h"
#include "qgsactionmanager.h"
#include "qgsaction.h"
#include "qgsvectorlayerutils.h"

#include <QPushButton>
#include <QSettings>
Expand Down Expand Up @@ -144,53 +145,34 @@ bool QgsFeatureAction::addFeature( const QgsAttributeMap& defaultAttributes, boo
if ( !mLayer || !mLayer->isEditable() )
return false;

QgsVectorDataProvider *provider = mLayer->dataProvider();
QgsAttributeList pkAttrList = mLayer->pkAttributeList();

QSettings settings;
bool reuseLastValues = settings.value( QStringLiteral( "/qgis/digitizing/reuseLastValues" ), false ).toBool();
QgsDebugMsg( QString( "reuseLastValues: %1" ).arg( reuseLastValues ) );

QgsExpressionContext context = mLayer->createExpressionContext();
QgsFields fields = mLayer->fields();
QgsAttributeMap initialAttributeValues;

// add the fields to the QgsFeature
const QgsFields& fields = mLayer->fields();
mFeature->initAttributes( fields.count() );
for ( int idx = 0; idx < fields.count(); ++idx )
{
QVariant v;

if ( defaultAttributes.contains( idx ) )
{
v = defaultAttributes.value( idx );
}
else if ( !mLayer->defaultValueExpression( idx ).isEmpty() )
{
// client side default expression set - use this in preference to reusing last value
v = mLayer->defaultValue( idx, *mFeature, &context );
initialAttributeValues.insert( idx, defaultAttributes.value( idx ) );
}
else if ( reuseLastValues && sLastUsedValues.contains( mLayer ) && sLastUsedValues[ mLayer ].contains( idx ) && !pkAttrList.contains( idx ) )
{
v = sLastUsedValues[ mLayer ][idx];
}
else
else if ( reuseLastValues && sLastUsedValues.contains( mLayer ) && sLastUsedValues[ mLayer ].contains( idx ) )
{
QVariant defaultLiteral = mLayer->dataProvider()->defaultValue( idx );
if ( defaultLiteral.isValid() )
{
v = defaultLiteral;
}
else
{
QString defaultClause = provider->defaultValueClause( idx );
if ( !defaultClause.isEmpty() )
v = defaultClause;
}
initialAttributeValues.insert( idx, sLastUsedValues[ mLayer ][idx] );
}

mFeature->setAttribute( idx, v );
}

// create new feature template - this will initialise the attributes to valid values, handling default
// values and field constraints
QgsExpressionContext context = mLayer->createExpressionContext();
QgsFeature newFeature = QgsVectorLayerUtils::createFeature( mLayer, mFeature->geometry(), initialAttributeValues,
&context );
*mFeature = newFeature;

//show the dialog to enter attribute values
//only show if enabled in settings and layer has fields
bool isDisabledAttributeValuesDlg = ( fields.count() == 0 ) || settings.value( QStringLiteral( "/qgis/digitizing/disable_enter_attribute_values_dialog" ), false ).toBool();
Expand Down
17 changes: 8 additions & 9 deletions src/app/qgsmaptoolfillring.cpp
Expand Up @@ -21,7 +21,7 @@
#include "qgsvectorlayer.h"
#include "qgsattributedialog.h"
#include "qgisapp.h"

#include "qgsvectorlayerutils.h"
#include <QMouseEvent>

#include <limits>
Expand Down Expand Up @@ -139,26 +139,26 @@ void QgsMapToolFillRing::cadCanvasReleaseEvent( QgsMapMouseEvent * e )
bBox.setXMaximum( xMax );
bBox.setYMaximum( yMax );

QgsExpressionContext context = vlayer->createExpressionContext();

QgsFeatureIterator fit = vlayer->getFeatures( QgsFeatureRequest().setFilterFid( modifiedFid ) );

QgsFeature f;
if ( fit.nextFeature( f ) )
{
//create QgsFeature with wkb representation
QgsFeature* ft = new QgsFeature( vlayer->fields(), 0 );

QgsGeometry g = QgsGeometry::fromPolygon( QgsPolygon() << pointList.toVector() );
ft->setGeometry( g );
ft->setAttributes( f.attributes() );

//create QgsFeature with wkb representation
QgsFeature ft = QgsVectorLayerUtils::createFeature( vlayer, g, f.attributes().toMap(), &context );

bool res = false;
if ( QApplication::keyboardModifiers() == Qt::ControlModifier )
{
res = vlayer->addFeature( *ft );
res = vlayer->addFeature( ft );
}
else
{
QgsAttributeDialog *dialog = new QgsAttributeDialog( vlayer, ft, false, nullptr, true );
QgsAttributeDialog *dialog = new QgsAttributeDialog( vlayer, &ft, false, nullptr, true );
dialog->setMode( QgsAttributeForm::AddFeatureMode );
res = dialog->exec(); // will also add the feature
}
Expand All @@ -169,7 +169,6 @@ void QgsMapToolFillRing::cadCanvasReleaseEvent( QgsMapMouseEvent * e )
}
else
{
delete ft;
vlayer->destroyEditCommand();
}
}
Expand Down
32 changes: 9 additions & 23 deletions src/app/qgsmergeattributesdialog.cpp
Expand Up @@ -111,7 +111,6 @@ void QgsMergeAttributesDialog::createTableWidgetContents()

//create combo boxes and insert attribute names
mFields = mVectorLayer->fields();
QSet<int> pkAttrList = mVectorLayer->pkAttributeList().toSet();

int col = 0;
mHiddenAttributes.clear();
Expand All @@ -127,7 +126,7 @@ void QgsMergeAttributesDialog::createTableWidgetContents()
mTableWidget->setColumnCount( col + 1 );

QComboBox *cb = createMergeComboBox( mFields.at( idx ).type() );
if ( pkAttrList.contains( idx ) )
if ( mFields.at( idx ).constraints().constraints() & QgsFieldConstraints::ConstraintUnique )
{
cb->setCurrentIndex( cb->findData( "skip" ) );
}
Expand Down Expand Up @@ -413,15 +412,17 @@ void QgsMergeAttributesDialog::on_mFromSelectedPushButton_clicked()
return;
}

QSet<int> pkAttributes = mVectorLayer->pkAttributeList().toSet();
for ( int i = 0; i < mTableWidget->columnCount(); ++i )
{
if ( pkAttributes.contains( i ) )
{
QComboBox* currentComboBox = qobject_cast<QComboBox *>( mTableWidget->cellWidget( 0, i ) );
if ( !currentComboBox )
continue;

if ( mVectorLayer->fields().at( i ).constraints().constraints() & QgsFieldConstraints::ConstraintUnique )
{
currentComboBox->setCurrentIndex( currentComboBox->findData( "skip" ) );
}
QComboBox* currentComboBox = qobject_cast<QComboBox *>( mTableWidget->cellWidget( 0, i ) );
if ( currentComboBox )
else
{
currentComboBox->setCurrentIndex( currentComboBox->findData( QStringLiteral( "f%1" ).arg( FID_TO_STRING( featureId ) ) ) );
}
Expand Down Expand Up @@ -528,21 +529,14 @@ QgsAttributes QgsMergeAttributesDialog::mergedAttributes() const
return QgsAttributes();
}

QgsExpressionContext context = mVectorLayer->createExpressionContext();

int widgetIndex = 0;
QgsAttributes results( mFields.count() );
for ( int fieldIdx = 0; fieldIdx < mFields.count(); ++fieldIdx )
{
if ( mHiddenAttributes.contains( fieldIdx ) )
{
//hidden attribute, set to default value
if ( !mVectorLayer->defaultValueExpression( fieldIdx ).isEmpty() )
results[fieldIdx] = mVectorLayer->defaultValue( fieldIdx, mFeatureList.at( 0 ), &context );
else if ( mVectorLayer->dataProvider() )
results[fieldIdx] = mVectorLayer->dataProvider()->defaultValueClause( fieldIdx );
else
results[fieldIdx] = QVariant();
results[fieldIdx] = QVariant();
continue;
}

Expand All @@ -561,14 +555,6 @@ QgsAttributes QgsMergeAttributesDialog::mergedAttributes() const
{
results[fieldIdx] = currentItem->data( Qt::DisplayRole );
}
else if ( !mVectorLayer->defaultValueExpression( fieldIdx ).isEmpty() )
{
results[fieldIdx] = mVectorLayer->defaultValue( fieldIdx, mFeatureList.at( 0 ), &context );
}
else if ( mVectorLayer->dataProvider() )
{
results[fieldIdx] = mVectorLayer->dataProvider()->defaultValueClause( fieldIdx );
}
widgetIndex++;
}

Expand Down
41 changes: 14 additions & 27 deletions src/core/qgsofflineediting.cpp
Expand Up @@ -32,6 +32,7 @@
#include "qgsslconnect.h"
#include "qgsfeatureiterator.h"
#include "qgslogger.h"
#include "qgsvectorlayerutils.h"

#include <QDir>
#include <QDomDocument>
Expand Down Expand Up @@ -747,21 +748,22 @@ void QgsOfflineEditing::applyAttributesAdded( QgsVectorLayer* remoteLayer, sqlit
void QgsOfflineEditing::applyFeaturesAdded( QgsVectorLayer* offlineLayer, QgsVectorLayer* remoteLayer, sqlite3* db, int layerId )
{
QString sql = QStringLiteral( "SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
QList<int> newFeatureIds = sqlQueryInts( db, sql );

QgsFields remoteFlds = remoteLayer->fields();
QList<int> featureIdInts = sqlQueryInts( db, sql );
QgsFeatureIds newFeatureIds;
Q_FOREACH ( int id, featureIdInts )
{
newFeatureIds << id;
}

QgsExpressionContext context = remoteLayer->createExpressionContext();

// get new features from offline layer
QgsFeatureList features;
for ( int i = 0; i < newFeatureIds.size(); i++ )
QgsFeatureIterator it = offlineLayer->getFeatures( QgsFeatureRequest().setFilterFids( newFeatureIds ) );
QgsFeature feature;
while ( it.nextFeature( feature ) )
{
QgsFeature feature;
if ( offlineLayer->getFeatures( QgsFeatureRequest().setFilterFid( newFeatureIds.at( i ) ) ).nextFeature( feature ) )
{
features << feature;
}
features << feature;
}

// copy features to remote layer
Expand All @@ -771,33 +773,18 @@ void QgsOfflineEditing::applyFeaturesAdded( QgsVectorLayer* offlineLayer, QgsVec
int newAttrsCount = remoteLayer->fields().count();
for ( QgsFeatureList::iterator it = features.begin(); it != features.end(); ++it )
{
QgsFeature f = *it;

// NOTE: Spatialite provider ignores position of geometry column
// restore gap in QgsAttributeMap if geometry column is not last (WORKAROUND)
QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
QgsAttributes newAttrs( newAttrsCount );
QgsAttributes attrs = f.attributes();
QgsAttributes attrs = it->attributes();
for ( int it = 0; it < attrs.count(); ++it )
{
newAttrs[ attrLookup[ it ] ] = attrs.at( it );
}

// try to use default value from the provider
// (important especially e.g. for postgis primary key generated from a sequence)
for ( int k = 0; k < newAttrs.count(); ++k )
{
if ( !newAttrs.at( k ).isNull() )
continue;

if ( !remoteLayer->defaultValueExpression( k ).isEmpty() )
newAttrs[k] = remoteLayer->defaultValue( k, f, &context );
else if ( remoteFlds.fieldOrigin( k ) == QgsFields::OriginProvider )
newAttrs[k] = remoteLayer->dataProvider()->defaultValueClause( remoteFlds.fieldOriginIndex( k ) );
}

f.setAttributes( newAttrs );

// respect constraints and provider default values
QgsFeature f = QgsVectorLayerUtils::createFeature( remoteLayer, it->geometry(), newAttrs.toMap(), &context );
remoteLayer->addFeature( f, false );

emit progressUpdated( i++ );
Expand Down

0 comments on commit 631bd48

Please sign in to comment.