Skip to content

Commit

Permalink
Respect non-enforced constraints when editing/adding features
Browse files Browse the repository at this point in the history
Warnings are shown, but features can be committed. Fields which
fail an unenforced constraint are now shaded in yellow to differentiate
from the red failure for enforced constraints.
  • Loading branch information
nyalldawson committed Nov 2, 2016
1 parent fac5bc0 commit 3f2a781
Show file tree
Hide file tree
Showing 27 changed files with 255 additions and 95 deletions.
3 changes: 2 additions & 1 deletion python/core/qgsvectorlayerutils.sip
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ class QgsVectorLayerUtils
/**
* Tests an attribute value to check whether it passes all constraints which are present on the corresponding field.
* Returns true if the attribute value is valid for the field. Any constraint failures will be reported in the errors argument.
* If the origin parameter is set then only constraints with a matching origin will be checked.
* If the strength or origin parameter is set then only constraints with a matching strength/origin will be checked.
*/
static bool validateAttribute( const QgsVectorLayer* layer, const QgsFeature& feature, int attributeIndex, QStringList& errors /Out/,
QgsFieldConstraints::ConstraintStrength strength = QgsFieldConstraints::ConstraintStrengthNotSet,
QgsFieldConstraints::ConstraintOrigin origin = QgsFieldConstraints::ConstraintOriginNotSet );

};
29 changes: 27 additions & 2 deletions python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ class QgsEditorWidgetWrapper : QgsWidgetWrapper
%End

public:

/**
* Result of constraint checks.
* @note added in QGIS 3.0
*/
enum ConstraintResult
{
ConstraintResultPass, //!< Widget passed constraints successfully
ConstraintResultFailHard, //!< Widget failed at least one hard (enforced) constraint
ConstraintResultFailSoft, //!< Widget failed at least one soft (non-enforced) constraint
};

/**
* Create a new widget wrapper
*
Expand Down Expand Up @@ -108,9 +120,18 @@ class QgsEditorWidgetWrapper : QgsWidgetWrapper
* false otherwise
* @note added in QGIS 2.16
* @see constraintFailureReason()
* @see isBlockingCommit()
*/
bool isValidConstraint() const;

/**
* Returns true if the widget is preventing the feature from being committed. This may be true as a result
* of attribute values failing enforced field constraints.
* @note added in QGIS 3.0
* @see isValidConstraint()
*/
bool isBlockingCommit() const;

/**
* Returns the reason why a constraint check has failed (or an empty string
* if constraint check was successful).
Expand All @@ -135,7 +156,7 @@ class QgsEditorWidgetWrapper : QgsWidgetWrapper
* @param err the error represented as a string. Empty if none.
* @param status
*/
void constraintStatusChanged( const QString& constraint, const QString& err, bool status );
void constraintStatusChanged( const QString& constraint, const QString &desc, const QString& err, ConstraintResult status );

public slots:
/**
Expand Down Expand Up @@ -214,6 +235,10 @@ class QgsEditorWidgetWrapper : QgsWidgetWrapper
*
* This can be overwritten in subclasses to allow individual widgets to
* change the visual cue.
*
* @param status The current constraint status.
*
* @note added in QGIS 2.16
*/
virtual void updateConstraintWidgetStatus( bool contraintValid );
virtual void updateConstraintWidgetStatus( ConstraintResult status );
};
14 changes: 2 additions & 12 deletions python/gui/editorwidgets/qgsrelationreferencewidgetwrapper.sip
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,6 @@ class QgsRelationReferenceWidgetWrapper : QgsEditorWidgetWrapper
virtual void setEnabled( bool enabled );

protected:
/**
* This should update the widget with a visual cue if a constraint status
* changed.
*
* By default a stylesheet will be applied on the widget that changes the
* background color to red.
*
* This can be overwritten in subclasses to allow individual widgets to
* change the visual cue.
* @note added in QGIS 2.16
*/
void updateConstraintWidgetStatus( bool constraintValid );

void updateConstraintWidgetStatus( ConstraintResult status );
};
6 changes: 5 additions & 1 deletion src/core/qgsvectorlayerutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ bool QgsVectorLayerUtils::valueExists( const QgsVectorLayer* layer, int fieldInd
return false;
}

bool QgsVectorLayerUtils::validateAttribute( const QgsVectorLayer* layer, const QgsFeature& feature, int attributeIndex, QStringList& errors, QgsFieldConstraints::ConstraintOrigin origin )
bool QgsVectorLayerUtils::validateAttribute( const QgsVectorLayer* layer, const QgsFeature& feature, int attributeIndex, QStringList& errors,
QgsFieldConstraints::ConstraintStrength strength, QgsFieldConstraints::ConstraintOrigin origin )
{
if ( !layer )
return false;
Expand All @@ -66,6 +67,7 @@ bool QgsVectorLayerUtils::validateAttribute( const QgsVectorLayer* layer, const
QgsFieldConstraints constraints = field.constraints();

if ( constraints.constraints() & QgsFieldConstraints::ConstraintExpression && !constraints.constraintExpression().isEmpty()
&& ( strength == QgsFieldConstraints::ConstraintStrengthNotSet || strength == constraints.constraintStrength( QgsFieldConstraints::ConstraintExpression ) )
&& ( origin == QgsFieldConstraints::ConstraintOriginNotSet || origin == constraints.constraintOrigin( QgsFieldConstraints::ConstraintExpression ) ) )
{
QgsExpressionContext context = layer->createExpressionContext();
Expand All @@ -90,6 +92,7 @@ bool QgsVectorLayerUtils::validateAttribute( const QgsVectorLayer* layer, const
}

if ( constraints.constraints() & QgsFieldConstraints::ConstraintNotNull
&& ( strength == QgsFieldConstraints::ConstraintStrengthNotSet || strength == constraints.constraintStrength( QgsFieldConstraints::ConstraintNotNull ) )
&& ( origin == QgsFieldConstraints::ConstraintOriginNotSet || origin == constraints.constraintOrigin( QgsFieldConstraints::ConstraintNotNull ) ) )
{
valid = valid && !value.isNull();
Expand All @@ -101,6 +104,7 @@ bool QgsVectorLayerUtils::validateAttribute( const QgsVectorLayer* layer, const
}

if ( constraints.constraints() & QgsFieldConstraints::ConstraintUnique
&& ( strength == QgsFieldConstraints::ConstraintStrengthNotSet || strength == constraints.constraintStrength( QgsFieldConstraints::ConstraintUnique ) )
&& ( origin == QgsFieldConstraints::ConstraintOriginNotSet || origin == constraints.constraintOrigin( QgsFieldConstraints::ConstraintUnique ) ) )
{
bool alreadyExists = QgsVectorLayerUtils::valueExists( layer, attributeIndex, value, QgsFeatureIds() << feature.id() );
Expand Down
3 changes: 2 additions & 1 deletion src/core/qgsvectorlayerutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ class CORE_EXPORT QgsVectorLayerUtils
/**
* Tests an attribute value to check whether it passes all constraints which are present on the corresponding field.
* Returns true if the attribute value is valid for the field. Any constraint failures will be reported in the errors argument.
* If the origin parameter is set then only constraints with a matching origin will be checked.
* If the strength or origin parameter is set then only constraints with a matching strength/origin will be checked.
*/
static bool validateAttribute( const QgsVectorLayer* layer, const QgsFeature& feature, int attributeIndex, QStringList& errors,
QgsFieldConstraints::ConstraintStrength strength = QgsFieldConstraints::ConstraintStrengthNotSet,
QgsFieldConstraints::ConstraintOrigin origin = QgsFieldConstraints::ConstraintOriginNotSet );

};
Expand Down
42 changes: 34 additions & 8 deletions src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
QgsEditorWidgetWrapper::QgsEditorWidgetWrapper( QgsVectorLayer* vl, int fieldIdx, QWidget* editor, QWidget* parent )
: QgsWidgetWrapper( vl, editor, parent )
, mValidConstraint( true )
, mIsBlockingCommit( false )
, mFieldIdx( fieldIdx )
{
}
Expand Down Expand Up @@ -98,12 +99,22 @@ void QgsEditorWidgetWrapper::valueChanged()
emit valueChanged( value() );
}

void QgsEditorWidgetWrapper::updateConstraintWidgetStatus( bool constraintValid )
void QgsEditorWidgetWrapper::updateConstraintWidgetStatus( ConstraintResult constraintResult )
{
if ( constraintValid )
widget()->setStyleSheet( QString() );
else
widget()->setStyleSheet( QStringLiteral( "background-color: #dd7777;" ) );
switch ( constraintResult )
{
case ConstraintResultPass:
widget()->setStyleSheet( QString() );
break;

case ConstraintResultFailHard:
widget()->setStyleSheet( QStringLiteral( "background-color: #dd7777;" ) );
break;

case ConstraintResultFailSoft:
widget()->setStyleSheet( QStringLiteral( "background-color: #ffd85d;" ) );
break;
}
}

void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft, QgsFieldConstraints::ConstraintOrigin constraintOrigin )
Expand Down Expand Up @@ -150,7 +161,15 @@ void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft, QgsFieldCon
}

QStringList errors;
mValidConstraint = QgsVectorLayerUtils::validateAttribute( layer(), ft, mFieldIdx, errors, constraintOrigin );
bool hardConstraintsOk = QgsVectorLayerUtils::validateAttribute( layer(), ft, mFieldIdx, errors, QgsFieldConstraints::ConstraintStrengthHard, constraintOrigin );

QStringList softErrors;
bool softConstraintsOk = QgsVectorLayerUtils::validateAttribute( layer(), ft, mFieldIdx, softErrors, QgsFieldConstraints::ConstraintStrengthSoft, constraintOrigin );
errors << softErrors;

mValidConstraint = hardConstraintsOk && softConstraintsOk;
mIsBlockingCommit = !hardConstraintsOk;

mConstraintFailureReason = errors.join( ", " );

if ( toEmit )
Expand All @@ -164,8 +183,10 @@ void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft, QgsFieldCon
else if ( !expressions.isEmpty() )
expressionDesc = expressions.at( 0 );

updateConstraintWidgetStatus( mValidConstraint );
emit constraintStatusChanged( expressionDesc, description, errStr, mValidConstraint );
ConstraintResult result = !hardConstraintsOk ? ConstraintResultFailHard
: ( !softConstraintsOk ? ConstraintResultFailSoft : ConstraintResultPass );
updateConstraintWidgetStatus( result );
emit constraintStatusChanged( expressionDesc, description, errStr, result );
}
}

Expand All @@ -174,6 +195,11 @@ bool QgsEditorWidgetWrapper::isValidConstraint() const
return mValidConstraint;
}

bool QgsEditorWidgetWrapper::isBlockingCommit() const
{
return mIsBlockingCommit;
}

QString QgsEditorWidgetWrapper::constraintFailureReason() const
{
return mConstraintFailureReason;
Expand Down
30 changes: 27 additions & 3 deletions src/gui/editorwidgets/core/qgseditorwidgetwrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,18 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
{
Q_OBJECT
public:

/**
* Result of constraint checks.
* @note added in QGIS 3.0
*/
enum ConstraintResult
{
ConstraintResultPass = 0, //!< Widget passed constraints successfully
ConstraintResultFailHard, //!< Widget failed at least one hard (enforced) constraint
ConstraintResultFailSoft, //!< Widget failed at least one soft (non-enforced) constraint
};

/**
* Create a new widget wrapper
*
Expand Down Expand Up @@ -130,9 +142,18 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
* false otherwise
* @note added in QGIS 2.16
* @see constraintFailureReason()
* @see isBlockingCommit()
*/
bool isValidConstraint() const;

/**
* Returns true if the widget is preventing the feature from being committed. This may be true as a result
* of attribute values failing enforced field constraints.
* @note added in QGIS 3.0
* @see isValidConstraint()
*/
bool isBlockingCommit() const;

/**
* Returns the reason why a constraint check has failed (or an empty string
* if constraint check was successful).
Expand All @@ -157,7 +178,7 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
* @param err the error represented as a string. Empty if none.
* @param status
*/
void constraintStatusChanged( const QString& constraint, const QString &desc, const QString& err, bool status );
void constraintStatusChanged( const QString& constraint, const QString &desc, const QString& err, ConstraintResult status );

public slots:
/**
Expand Down Expand Up @@ -237,11 +258,11 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
* This can be overwritten in subclasses to allow individual widgets to
* change the visual cue.
*
* @param constraintValid The current constraint status.
* @param status The current constraint status.
*
* @note added in QGIS 2.16
*/
virtual void updateConstraintWidgetStatus( bool constraintValid );
virtual void updateConstraintWidgetStatus( ConstraintResult status );

private:

Expand All @@ -250,6 +271,9 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
*/
bool mValidConstraint;

//! True if widget is blocking feature commits
bool mIsBlockingCommit;

//! Contains the string explanation of why a constraint check failed
QString mConstraintFailureReason;

Expand Down
2 changes: 1 addition & 1 deletion src/gui/editorwidgets/qgscolorwidgetwrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ void QgsColorWidgetWrapper::setValue( const QVariant& value )
mColorButton->setColor( !value.isNull() ? QColor( value.toString() ) : QColor() );
}

void QgsColorWidgetWrapper::updateConstraintWidgetStatus( bool /*constraintValid*/ )
void QgsColorWidgetWrapper::updateConstraintWidgetStatus( ConstraintResult /*constraintValid*/ )
{
// nothing
}
2 changes: 1 addition & 1 deletion src/gui/editorwidgets/qgscolorwidgetwrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class GUI_EXPORT QgsColorWidgetWrapper : public QgsEditorWidgetWrapper
void setValue( const QVariant& value ) override;

private:
void updateConstraintWidgetStatus( bool constraintValid ) override;
void updateConstraintWidgetStatus( ConstraintResult status ) override;

QgsColorButton* mColorButton;
};
Expand Down
20 changes: 15 additions & 5 deletions src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,23 @@ void QgsExternalResourceWidgetWrapper::setEnabled( bool enabled )
mQgsWidget->setReadOnly( !enabled );
}

void QgsExternalResourceWidgetWrapper::updateConstraintWidgetStatus( bool constraintValid )
void QgsExternalResourceWidgetWrapper::updateConstraintWidgetStatus( ConstraintResult status )
{
if ( mLineEdit )
{
if ( constraintValid )
mLineEdit->setStyleSheet( QString() );
else
mLineEdit->setStyleSheet( QStringLiteral( "QgsFilterLineEdit { background-color: #dd7777; }" ) );
switch ( status )
{
case ConstraintResultPass:
mLineEdit->setStyleSheet( QString() );
break;

case ConstraintResultFailHard:
mLineEdit->setStyleSheet( QStringLiteral( "QgsFilterLineEdit { background-color: #dd7777; }" ) );
break;

case ConstraintResultFailSoft:
mLineEdit->setStyleSheet( QStringLiteral( "QgsFilterLineEdit { background-color: #ffd85d; }" ) );
break;
}
}
}
2 changes: 1 addition & 1 deletion src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class GUI_EXPORT QgsExternalResourceWidgetWrapper : public QgsEditorWidgetWrappe
void setEnabled( bool enabled ) override;

private:
void updateConstraintWidgetStatus( bool constraintValid ) override;
void updateConstraintWidgetStatus( ConstraintResult status ) override;

QLineEdit* mLineEdit;
QLabel* mLabel;
Expand Down
18 changes: 13 additions & 5 deletions src/gui/editorwidgets/qgsfilenamewidgetwrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,15 +152,23 @@ void QgsFileNameWidgetWrapper::selectFileName()
mLineEdit->setText( fileName );
}

void QgsFileNameWidgetWrapper::updateConstraintWidgetStatus( bool constraintValid )
void QgsFileNameWidgetWrapper::updateConstraintWidgetStatus( ConstraintResult status )
{
if ( mLineEdit )
{
if ( constraintValid )
mLineEdit->setStyleSheet( QString() );
else
switch ( status )
{
mLineEdit->setStyleSheet( QStringLiteral( "QgsFilterLineEdit { background-color: #dd7777; }" ) );
case ConstraintResultPass:
mLineEdit->setStyleSheet( QString() );
break;

case ConstraintResultFailHard:
mLineEdit->setStyleSheet( QStringLiteral( "QgsFilterLineEdit { background-color: #dd7777; }" ) );
break;

case ConstraintResultFailSoft:
mLineEdit->setStyleSheet( QStringLiteral( "QgsFilterLineEdit { background-color: #ffd85d; }" ) );
break;
}
}
}
2 changes: 1 addition & 1 deletion src/gui/editorwidgets/qgsfilenamewidgetwrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class GUI_EXPORT QgsFileNameWidgetWrapper : public QgsEditorWidgetWrapper
void setValue( const QVariant& value ) override;

private:
void updateConstraintWidgetStatus( bool constraintValid ) override;
void updateConstraintWidgetStatus( ConstraintResult status ) override;

QLineEdit* mLineEdit;
QPushButton* mPushButton;
Expand Down
2 changes: 1 addition & 1 deletion src/gui/editorwidgets/qgskeyvaluewidgetwrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ void QgsKeyValueWidgetWrapper::setValue( const QVariant& value )
mWidget->setMap( value.toMap() );
}

void QgsKeyValueWidgetWrapper::updateConstraintWidgetStatus( bool /*constraintValid*/ )
void QgsKeyValueWidgetWrapper::updateConstraintWidgetStatus( ConstraintResult /*constraintValid*/ )
{
// Nothing
}
Expand Down
2 changes: 1 addition & 1 deletion src/gui/editorwidgets/qgskeyvaluewidgetwrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class GUI_EXPORT QgsKeyValueWidgetWrapper : public QgsEditorWidgetWrapper
void onValueChanged();

private:
void updateConstraintWidgetStatus( bool constraintValid ) override;
void updateConstraintWidgetStatus( ConstraintResult status ) override;

QgsKeyValueWidget* mWidget;
};
Expand Down
Loading

0 comments on commit 3f2a781

Please sign in to comment.