Skip to content

Commit 993fa38

Browse files
authoredJan 15, 2020
Merge pull request #33688 from signedav/copy-child
Fix invalid attributes dialog on copy to another layer
2 parents ddf3d2d + 2215fde commit 993fa38

File tree

4 files changed

+278
-2
lines changed

4 files changed

+278
-2
lines changed
 

‎src/app/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ SET(QGIS_APP_SRCS
4646
qgsdisplayangle.cpp
4747
qgsfieldcalculator.cpp
4848
qgsfirstrundialog.cpp
49+
qgsfixattributedialog.cpp
4950
qgsgeometryvalidationservice.cpp
5051
qgsgeometryvalidationdock.cpp
5152
qgsgeometryvalidationmodel.cpp

‎src/app/qgisapp.cpp

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
#include "qgssourceselectproviderregistry.h"
8888
#include "qgssourceselectprovider.h"
8989
#include "qgsprovidermetadata.h"
90+
#include "qgsfixattributedialog.h"
9091

9192
#include "qgsanalysis.h"
9293
#include "qgsgeometrycheckregistry.h"
@@ -9611,6 +9612,59 @@ void QgisApp::pasteFromClipboard( QgsMapLayer *destinationLayer )
96119612
// now create new feature using pasted feature as a template. This automatically handles default
96129613
// values and field constraints
96139614
QgsFeatureList newFeatures {QgsVectorLayerUtils::createFeatures( pasteVectorLayer, newFeaturesDataList, &context )};
9615+
9616+
// check constraints
9617+
bool hasStrongConstraints = false;
9618+
9619+
for ( const QgsField &field : pasteVectorLayer->fields() )
9620+
{
9621+
if ( ( field.constraints().constraints() & QgsFieldConstraints::ConstraintUnique && field.constraints().constraintStrength( QgsFieldConstraints::ConstraintUnique ) & QgsFieldConstraints::ConstraintStrengthHard )
9622+
|| ( field.constraints().constraints() & QgsFieldConstraints::ConstraintNotNull && field.constraints().constraintStrength( QgsFieldConstraints::ConstraintNotNull ) & QgsFieldConstraints::ConstraintStrengthHard )
9623+
|| ( field.constraints().constraints() & QgsFieldConstraints::ConstraintExpression && !field.constraints().constraintExpression().isEmpty() && field.constraints().constraintStrength( QgsFieldConstraints::ConstraintExpression ) & QgsFieldConstraints::ConstraintStrengthHard )
9624+
)
9625+
hasStrongConstraints = true;
9626+
}
9627+
9628+
if ( hasStrongConstraints )
9629+
{
9630+
QgsFeatureList validFeatures = newFeatures;
9631+
QgsFeatureList invalidFeatures;
9632+
QMutableListIterator<QgsFeature> it( validFeatures );
9633+
while ( it.hasNext() )
9634+
{
9635+
QgsFeature &f = it.next();
9636+
for ( int idx = 0; idx < pasteVectorLayer->fields().count(); ++idx )
9637+
{
9638+
QStringList errors;
9639+
if ( !QgsVectorLayerUtils::validateAttribute( pasteVectorLayer, f, idx, errors, QgsFieldConstraints::ConstraintStrengthHard, QgsFieldConstraints::ConstraintOriginNotSet ) )
9640+
{
9641+
invalidFeatures << f;
9642+
it.remove();
9643+
break;
9644+
}
9645+
}
9646+
}
9647+
9648+
if ( !invalidFeatures.isEmpty() )
9649+
{
9650+
newFeatures.clear();
9651+
9652+
QgsFixAttributeDialog *dialog = new QgsFixAttributeDialog( pasteVectorLayer, invalidFeatures, this );
9653+
int feedback = dialog->exec();
9654+
9655+
switch ( feedback )
9656+
{
9657+
case QgsFixAttributeDialog::PasteValid:
9658+
//paste valid and fixed, vanish unfixed
9659+
newFeatures << validFeatures << dialog->fixedFeatures();
9660+
break;
9661+
case QgsFixAttributeDialog::PasteAll:
9662+
//paste all, even unfixed
9663+
newFeatures << validFeatures << dialog->fixedFeatures() << dialog->unfixedFeatures();
9664+
break;
9665+
}
9666+
}
9667+
}
96149668
pasteVectorLayer->addFeatures( newFeatures );
96159669
QgsFeatureIds newIds;
96169670
newIds.reserve( newFeatures.size() );
@@ -9623,8 +9677,8 @@ void QgisApp::pasteFromClipboard( QgsMapLayer *destinationLayer )
96239677
pasteVectorLayer->endEditCommand();
96249678
pasteVectorLayer->updateExtents();
96259679

9626-
int nCopiedFeatures = features.count();
9627-
Qgis::MessageLevel level = ( nCopiedFeatures == 0 || nCopiedFeatures < nTotalFeatures || invalidGeometriesCount > 0 ) ? Qgis::Warning : Qgis::Info;
9680+
int nCopiedFeatures = newFeatures.count();
9681+
Qgis::MessageLevel level = ( nCopiedFeatures == 0 || invalidGeometriesCount > 0 ) ? Qgis::Warning : Qgis::Info;
96289682
QString message;
96299683
if ( nCopiedFeatures == 0 )
96309684
{

‎src/app/qgsfixattributedialog.cpp

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/***************************************************************************
2+
qgsfixattributedialog.cpp
3+
---------------------
4+
begin : January 2020
5+
copyright : (C) 2020 by David Signer
6+
email : david at opengis dot ch
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
#include "qgsfixattributedialog.h"
16+
17+
#include "qgsattributeform.h"
18+
#include "qgsapplication.h"
19+
20+
#include <QtWidgets/QPushButton>
21+
22+
QgsFixAttributeDialog::QgsFixAttributeDialog( QgsVectorLayer *vl, QgsFeatureList &features, QWidget *parent )
23+
: QDialog( parent )
24+
, mFeatures( features )
25+
{
26+
init( vl );
27+
}
28+
29+
void QgsFixAttributeDialog::init( QgsVectorLayer *layer )
30+
{
31+
QgsAttributeEditorContext context;
32+
setWindowTitle( tr( "%1 - Fix Pasted Features" ).arg( layer->name() ) );
33+
setLayout( new QGridLayout() );
34+
layout()->setMargin( 0 );
35+
context.setFormMode( QgsAttributeEditorContext::StandaloneDialog );
36+
37+
mUnfixedFeatures = mFeatures;
38+
mCurrentFeature = mFeatures.begin();
39+
40+
QGridLayout *infoLayout = new QGridLayout();
41+
QWidget *infoBox = new QWidget();
42+
infoBox->setLayout( infoLayout );
43+
layout()->addWidget( infoBox );
44+
45+
mDescription = new QLabel( descriptionText() );
46+
mDescription->setVisible( mFeatures.count() > 1 );
47+
infoLayout->addWidget( mDescription );
48+
mProgressBar = new QProgressBar();
49+
mProgressBar->setOrientation( Qt::Horizontal );
50+
mProgressBar->setRange( 0, mFeatures.count() );
51+
mProgressBar->setVisible( mFeatures.count() > 1 );
52+
infoLayout->addWidget( mProgressBar );
53+
QgsFeature feature;
54+
mAttributeForm = new QgsAttributeForm( layer, *mCurrentFeature, context, this );
55+
mAttributeForm->setMode( QgsAttributeEditorContext::SingleEditMode );
56+
mAttributeForm->disconnectButtonBox();
57+
layout()->addWidget( mAttributeForm );
58+
59+
QDialogButtonBox *buttonBox = mAttributeForm->findChild<QDialogButtonBox *>();
60+
QPushButton *cancelAllBtn = new QPushButton( tr( "Discard All" ) );
61+
QPushButton *cancelAllInvalidBtn = new QPushButton( tr( "Discard All Invalid" ) );
62+
QPushButton *storeAllInvalidBtn = new QPushButton( tr( "Paste All (Including Invalid)" ) );
63+
if ( mFeatures.count() > 1 )
64+
{
65+
buttonBox->addButton( cancelAllBtn, QDialogButtonBox::ActionRole );
66+
buttonBox->addButton( cancelAllInvalidBtn, QDialogButtonBox::ActionRole );
67+
connect( cancelAllBtn, &QAbstractButton::clicked, this, [ = ]()
68+
{
69+
done( DiscardAll );
70+
} );
71+
connect( cancelAllInvalidBtn, &QAbstractButton::clicked, this, [ = ]()
72+
{
73+
done( PasteValid );
74+
} );
75+
buttonBox->button( QDialogButtonBox::Cancel )->setText( tr( "Skip" ) );
76+
}
77+
else
78+
{
79+
storeAllInvalidBtn->setText( tr( "Paste Anyway" ) );
80+
}
81+
buttonBox->addButton( storeAllInvalidBtn, QDialogButtonBox::ActionRole );
82+
connect( storeAllInvalidBtn, &QAbstractButton::clicked, this, [ = ]()
83+
{
84+
done( PasteAll );
85+
} );
86+
connect( buttonBox, &QDialogButtonBox::rejected, this, &QgsFixAttributeDialog::reject );
87+
connect( buttonBox, &QDialogButtonBox::accepted, this, &QgsFixAttributeDialog::accept );
88+
connect( layer, &QObject::destroyed, this, &QWidget::close );
89+
90+
focusNextChild();
91+
}
92+
93+
QString QgsFixAttributeDialog::descriptionText()
94+
{
95+
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() );
96+
}
97+
98+
void QgsFixAttributeDialog::accept()
99+
{
100+
mAttributeForm->save();
101+
mFixedFeatures << mAttributeForm->feature();
102+
mUnfixedFeatures.removeOne( *mCurrentFeature );
103+
104+
//next feature
105+
++mCurrentFeature;
106+
if ( mCurrentFeature != mFeatures.end() )
107+
{
108+
mAttributeForm->setFeature( *mCurrentFeature );
109+
}
110+
else
111+
{
112+
done( PasteValid );
113+
}
114+
115+
mProgressBar->setValue( mCurrentFeature - mFeatures.begin() );
116+
mDescription->setText( descriptionText() );
117+
}
118+
119+
void QgsFixAttributeDialog::reject()
120+
{
121+
//next feature
122+
++mCurrentFeature;
123+
if ( mCurrentFeature != mFeatures.end() )
124+
{
125+
mAttributeForm->setFeature( *mCurrentFeature );
126+
}
127+
else
128+
{
129+
done( PasteValid );
130+
}
131+
132+
mProgressBar->setValue( mCurrentFeature - mFeatures.begin() );
133+
mDescription->setText( descriptionText() );
134+
}

‎src/app/qgsfixattributedialog.h

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/***************************************************************************
2+
qgsfixattributedialog.h
3+
---------------------
4+
begin : January 2020
5+
copyright : (C) 2020 by David Signer
6+
email : david at opengis dot ch
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#ifndef QGSFIXATTRIBUTEDIALOG_H
17+
#define QGSFIXATTRIBUTEDIALOG_H
18+
19+
#include "qgsattributeeditorcontext.h"
20+
#include "qgis_sip.h"
21+
#include "qgsattributeform.h"
22+
#include "qgstrackedvectorlayertools.h"
23+
24+
#include <QDialog>
25+
#include <QGridLayout>
26+
#include <QProgressBar>
27+
#include "qgis_gui.h"
28+
29+
/**
30+
* \ingroup gui
31+
* \class QgsFixAttributeDialog
32+
* \brief Dialog to fix a list of invalid feature regarding constraints
33+
* \since QGIS 3.12
34+
*/
35+
36+
class GUI_EXPORT QgsFixAttributeDialog : public QDialog
37+
{
38+
Q_OBJECT
39+
40+
public:
41+
42+
/**
43+
* Feedback code on closing the dialog
44+
*/
45+
enum Feedback
46+
{
47+
DiscardAll, //!< Feedback to discard all features (even valid ones)
48+
PasteValid, //!< Feedback to paste the valid features and vanishe the invalid ones
49+
PasteAll //!< Feedback to paste all features, no matter if valid or invalid
50+
};
51+
52+
/**
53+
* Constructor for QgsFixAttributeDialog
54+
*/
55+
QgsFixAttributeDialog( QgsVectorLayer *vl, QgsFeatureList &features, QWidget *parent SIP_TRANSFERTHIS = nullptr );
56+
57+
/**
58+
* Returns fixed features
59+
*/
60+
QgsFeatureList fixedFeatures() { return mFixedFeatures; }
61+
62+
/**
63+
* Returns unfixed features (canceled or not handeled)
64+
*/
65+
QgsFeatureList unfixedFeatures() { return mUnfixedFeatures; }
66+
67+
public slots:
68+
void accept() override;
69+
void reject() override;
70+
71+
private:
72+
void init( QgsVectorLayer *layer );
73+
QString descriptionText();
74+
75+
QgsFeatureList mFeatures;
76+
QgsFeatureList::iterator mCurrentFeature;
77+
78+
QgsFeatureList mFixedFeatures;
79+
QgsFeatureList mUnfixedFeatures;
80+
81+
QgsAttributeForm *mAttributeForm = nullptr;
82+
QProgressBar *mProgressBar = nullptr;
83+
QLabel *mDescription = nullptr;
84+
};
85+
86+
#endif // QGSFIXATTRIBUTEDIALOG_H
87+

0 commit comments

Comments
 (0)
Please sign in to comment.