Skip to content

Commit

Permalink
Merge pull request #33688 from signedav/copy-child
Browse files Browse the repository at this point in the history
Fix invalid attributes dialog on copy to another layer
  • Loading branch information
signedav committed Jan 15, 2020
2 parents ddf3d2d + 2215fde commit 993fa38
Show file tree
Hide file tree
Showing 4 changed files with 278 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/app/CMakeLists.txt
Expand Up @@ -46,6 +46,7 @@ SET(QGIS_APP_SRCS
qgsdisplayangle.cpp
qgsfieldcalculator.cpp
qgsfirstrundialog.cpp
qgsfixattributedialog.cpp
qgsgeometryvalidationservice.cpp
qgsgeometryvalidationdock.cpp
qgsgeometryvalidationmodel.cpp
Expand Down
58 changes: 56 additions & 2 deletions src/app/qgisapp.cpp
Expand Up @@ -87,6 +87,7 @@
#include "qgssourceselectproviderregistry.h"
#include "qgssourceselectprovider.h"
#include "qgsprovidermetadata.h"
#include "qgsfixattributedialog.h"

#include "qgsanalysis.h"
#include "qgsgeometrycheckregistry.h"
Expand Down Expand Up @@ -9611,6 +9612,59 @@ void QgisApp::pasteFromClipboard( QgsMapLayer *destinationLayer )
// now create new feature using pasted feature as a template. This automatically handles default
// values and field constraints
QgsFeatureList newFeatures {QgsVectorLayerUtils::createFeatures( pasteVectorLayer, newFeaturesDataList, &context )};

// check constraints
bool hasStrongConstraints = false;

for ( const QgsField &field : pasteVectorLayer->fields() )
{
if ( ( field.constraints().constraints() & QgsFieldConstraints::ConstraintUnique && field.constraints().constraintStrength( QgsFieldConstraints::ConstraintUnique ) & QgsFieldConstraints::ConstraintStrengthHard )
|| ( field.constraints().constraints() & QgsFieldConstraints::ConstraintNotNull && field.constraints().constraintStrength( QgsFieldConstraints::ConstraintNotNull ) & QgsFieldConstraints::ConstraintStrengthHard )
|| ( field.constraints().constraints() & QgsFieldConstraints::ConstraintExpression && !field.constraints().constraintExpression().isEmpty() && field.constraints().constraintStrength( QgsFieldConstraints::ConstraintExpression ) & QgsFieldConstraints::ConstraintStrengthHard )
)
hasStrongConstraints = true;
}

if ( hasStrongConstraints )
{
QgsFeatureList validFeatures = newFeatures;
QgsFeatureList invalidFeatures;
QMutableListIterator<QgsFeature> it( validFeatures );
while ( it.hasNext() )
{
QgsFeature &f = it.next();
for ( int idx = 0; idx < pasteVectorLayer->fields().count(); ++idx )
{
QStringList errors;
if ( !QgsVectorLayerUtils::validateAttribute( pasteVectorLayer, f, idx, errors, QgsFieldConstraints::ConstraintStrengthHard, QgsFieldConstraints::ConstraintOriginNotSet ) )
{
invalidFeatures << f;
it.remove();
break;
}
}
}

if ( !invalidFeatures.isEmpty() )
{
newFeatures.clear();

QgsFixAttributeDialog *dialog = new QgsFixAttributeDialog( pasteVectorLayer, invalidFeatures, this );
int feedback = dialog->exec();

switch ( feedback )
{
case QgsFixAttributeDialog::PasteValid:
//paste valid and fixed, vanish unfixed
newFeatures << validFeatures << dialog->fixedFeatures();
break;
case QgsFixAttributeDialog::PasteAll:
//paste all, even unfixed
newFeatures << validFeatures << dialog->fixedFeatures() << dialog->unfixedFeatures();
break;
}
}
}
pasteVectorLayer->addFeatures( newFeatures );
QgsFeatureIds newIds;
newIds.reserve( newFeatures.size() );
Expand All @@ -9623,8 +9677,8 @@ void QgisApp::pasteFromClipboard( QgsMapLayer *destinationLayer )
pasteVectorLayer->endEditCommand();
pasteVectorLayer->updateExtents();

int nCopiedFeatures = features.count();
Qgis::MessageLevel level = ( nCopiedFeatures == 0 || nCopiedFeatures < nTotalFeatures || invalidGeometriesCount > 0 ) ? Qgis::Warning : Qgis::Info;
int nCopiedFeatures = newFeatures.count();
Qgis::MessageLevel level = ( nCopiedFeatures == 0 || invalidGeometriesCount > 0 ) ? Qgis::Warning : Qgis::Info;
QString message;
if ( nCopiedFeatures == 0 )
{
Expand Down
134 changes: 134 additions & 0 deletions src/app/qgsfixattributedialog.cpp
@@ -0,0 +1,134 @@
/***************************************************************************
qgsfixattributedialog.cpp
---------------------
begin : January 2020
copyright : (C) 2020 by David Signer
email : david at opengis dot ch
***************************************************************************
* *
* 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 "qgsfixattributedialog.h"

#include "qgsattributeform.h"
#include "qgsapplication.h"

#include <QtWidgets/QPushButton>

QgsFixAttributeDialog::QgsFixAttributeDialog( QgsVectorLayer *vl, QgsFeatureList &features, QWidget *parent )
: QDialog( parent )
, mFeatures( features )
{
init( vl );
}

void QgsFixAttributeDialog::init( QgsVectorLayer *layer )
{
QgsAttributeEditorContext context;
setWindowTitle( tr( "%1 - Fix Pasted Features" ).arg( layer->name() ) );
setLayout( new QGridLayout() );
layout()->setMargin( 0 );
context.setFormMode( QgsAttributeEditorContext::StandaloneDialog );

mUnfixedFeatures = mFeatures;
mCurrentFeature = mFeatures.begin();

QGridLayout *infoLayout = new QGridLayout();
QWidget *infoBox = new QWidget();
infoBox->setLayout( infoLayout );
layout()->addWidget( infoBox );

mDescription = new QLabel( descriptionText() );
mDescription->setVisible( mFeatures.count() > 1 );
infoLayout->addWidget( mDescription );
mProgressBar = new QProgressBar();
mProgressBar->setOrientation( Qt::Horizontal );
mProgressBar->setRange( 0, mFeatures.count() );
mProgressBar->setVisible( mFeatures.count() > 1 );
infoLayout->addWidget( mProgressBar );
QgsFeature feature;
mAttributeForm = new QgsAttributeForm( layer, *mCurrentFeature, context, this );
mAttributeForm->setMode( QgsAttributeEditorContext::SingleEditMode );
mAttributeForm->disconnectButtonBox();
layout()->addWidget( mAttributeForm );

QDialogButtonBox *buttonBox = mAttributeForm->findChild<QDialogButtonBox *>();
QPushButton *cancelAllBtn = new QPushButton( tr( "Discard All" ) );
QPushButton *cancelAllInvalidBtn = new QPushButton( tr( "Discard All Invalid" ) );
QPushButton *storeAllInvalidBtn = new QPushButton( tr( "Paste All (Including Invalid)" ) );
if ( mFeatures.count() > 1 )
{
buttonBox->addButton( cancelAllBtn, QDialogButtonBox::ActionRole );
buttonBox->addButton( cancelAllInvalidBtn, QDialogButtonBox::ActionRole );
connect( cancelAllBtn, &QAbstractButton::clicked, this, [ = ]()
{
done( DiscardAll );
} );
connect( cancelAllInvalidBtn, &QAbstractButton::clicked, this, [ = ]()
{
done( PasteValid );
} );
buttonBox->button( QDialogButtonBox::Cancel )->setText( tr( "Skip" ) );
}
else
{
storeAllInvalidBtn->setText( tr( "Paste Anyway" ) );
}
buttonBox->addButton( storeAllInvalidBtn, QDialogButtonBox::ActionRole );
connect( storeAllInvalidBtn, &QAbstractButton::clicked, this, [ = ]()
{
done( PasteAll );
} );
connect( buttonBox, &QDialogButtonBox::rejected, this, &QgsFixAttributeDialog::reject );
connect( buttonBox, &QDialogButtonBox::accepted, this, &QgsFixAttributeDialog::accept );
connect( layer, &QObject::destroyed, this, &QWidget::close );

focusNextChild();
}

QString QgsFixAttributeDialog::descriptionText()
{
return tr( "%1 of %2 features processed (%3 fixed, %4 skipped)" ).arg( mCurrentFeature - mFeatures.begin() ).arg( mFeatures.count() ).arg( mFixedFeatures.count() ).arg( mCurrentFeature - mFeatures.begin() - mFixedFeatures.count() );
}

void QgsFixAttributeDialog::accept()
{
mAttributeForm->save();
mFixedFeatures << mAttributeForm->feature();
mUnfixedFeatures.removeOne( *mCurrentFeature );

//next feature
++mCurrentFeature;
if ( mCurrentFeature != mFeatures.end() )
{
mAttributeForm->setFeature( *mCurrentFeature );
}
else
{
done( PasteValid );
}

mProgressBar->setValue( mCurrentFeature - mFeatures.begin() );
mDescription->setText( descriptionText() );
}

void QgsFixAttributeDialog::reject()
{
//next feature
++mCurrentFeature;
if ( mCurrentFeature != mFeatures.end() )
{
mAttributeForm->setFeature( *mCurrentFeature );
}
else
{
done( PasteValid );
}

mProgressBar->setValue( mCurrentFeature - mFeatures.begin() );
mDescription->setText( descriptionText() );
}
87 changes: 87 additions & 0 deletions src/app/qgsfixattributedialog.h
@@ -0,0 +1,87 @@
/***************************************************************************
qgsfixattributedialog.h
---------------------
begin : January 2020
copyright : (C) 2020 by David Signer
email : david at opengis dot ch
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#ifndef QGSFIXATTRIBUTEDIALOG_H
#define QGSFIXATTRIBUTEDIALOG_H

#include "qgsattributeeditorcontext.h"
#include "qgis_sip.h"
#include "qgsattributeform.h"
#include "qgstrackedvectorlayertools.h"

#include <QDialog>
#include <QGridLayout>
#include <QProgressBar>
#include "qgis_gui.h"

/**
* \ingroup gui
* \class QgsFixAttributeDialog
* \brief Dialog to fix a list of invalid feature regarding constraints
* \since QGIS 3.12
*/

class GUI_EXPORT QgsFixAttributeDialog : public QDialog
{
Q_OBJECT

public:

/**
* Feedback code on closing the dialog
*/
enum Feedback
{
DiscardAll, //!< Feedback to discard all features (even valid ones)
PasteValid, //!< Feedback to paste the valid features and vanishe the invalid ones
PasteAll //!< Feedback to paste all features, no matter if valid or invalid
};

/**
* Constructor for QgsFixAttributeDialog
*/
QgsFixAttributeDialog( QgsVectorLayer *vl, QgsFeatureList &features, QWidget *parent SIP_TRANSFERTHIS = nullptr );

/**
* Returns fixed features
*/
QgsFeatureList fixedFeatures() { return mFixedFeatures; }

/**
* Returns unfixed features (canceled or not handeled)
*/
QgsFeatureList unfixedFeatures() { return mUnfixedFeatures; }

public slots:
void accept() override;
void reject() override;

private:
void init( QgsVectorLayer *layer );
QString descriptionText();

QgsFeatureList mFeatures;
QgsFeatureList::iterator mCurrentFeature;

QgsFeatureList mFixedFeatures;
QgsFeatureList mUnfixedFeatures;

QgsAttributeForm *mAttributeForm = nullptr;
QProgressBar *mProgressBar = nullptr;
QLabel *mDescription = nullptr;
};

#endif // QGSFIXATTRIBUTEDIALOG_H

0 comments on commit 993fa38

Please sign in to comment.