Skip to content

Commit 020d20a

Browse files
committedMay 31, 2016
[FEATURE] constraints on widgets
1 parent 4ae1b55 commit 020d20a

29 files changed

+782
-41
lines changed
 

‎python/core/qgseditformconfig.sip

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,22 @@ class QgsEditFormConfig : QObject
476476
*/
477477
void setReadOnly( int idx, bool readOnly = true );
478478

479+
/**
480+
* Returns the constraint expression of a specific field
481+
* @param idx The index of the field
482+
* @return the expression
483+
* @note added in QGIS 2.16
484+
*/
485+
QString constraint( int idx ) const;
486+
487+
/**
488+
* Set the constraint expression for a specific field
489+
* @param idx the field index
490+
* @param str the constraint expression
491+
* @note added in QGIS 2.16
492+
*/
493+
void setConstraint( int idx, const QString& str );
494+
479495
/**
480496
* Returns if the field at fieldidx should be treated as NOT NULL value
481497
*/

‎python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,21 @@ class QgsEditorWidgetWrapper : QgsWidgetWrapper
8888
*/
8989
virtual void showIndeterminateState();
9090

91+
/**
92+
* Update constraint.
93+
* @param featureContext the feature to use to evaluate the constraint
94+
* @note added in QGIS 2.16
95+
*/
96+
void updateConstraint( const QgsFeature &featureContext );
97+
98+
/**
99+
* Get the current constraint status.
100+
* @return true if the constraint is valid or if there's not constraint,
101+
* false otherwise
102+
* @note added in QGIS 2.16
103+
*/
104+
bool isValidConstraint() const;
105+
91106
signals:
92107
/**
93108
* Emit this signal, whenever the value changed.
@@ -96,6 +111,13 @@ class QgsEditorWidgetWrapper : QgsWidgetWrapper
96111
*/
97112
void valueChanged( const QVariant& value );
98113

114+
/**
115+
* @brief constraintStatusChanged
116+
* @param constraint
117+
* @param status
118+
*/
119+
void constraintStatusChanged( const QString& constraint, const QString& err, bool status );
120+
99121
public slots:
100122
/**
101123
* Will be called when the feature changes
@@ -162,4 +184,17 @@ class QgsEditorWidgetWrapper : QgsWidgetWrapper
162184
* Will call the value() method to determine the emitted value
163185
*/
164186
void valueChanged();
187+
188+
protected:
189+
/**
190+
* This should update the widget with a visual cue if a constraint status
191+
* changed.
192+
*
193+
* By default a stylesheet will be applied on the widget that changes the
194+
* background color to red.
195+
*
196+
* This can be overwritten in subclasses to allow individual widgets to
197+
* change the visual cue.
198+
*/
199+
virtual void updateConstraintWidgetStatus();
165200
};

‎python/gui/editorwidgets/qgsrelationreferencewidgetwrapper.sip

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,18 @@ class QgsRelationReferenceWidgetWrapper : QgsEditorWidgetWrapper
2121
public slots:
2222
virtual void setValue( const QVariant& value );
2323
virtual void setEnabled( bool enabled );
24+
25+
protected:
26+
/**
27+
* This should update the widget with a visual cue if a constraint status
28+
* changed.
29+
*
30+
* By default a stylesheet will be applied on the widget that changes the
31+
* background color to red.
32+
*
33+
* This can be overwritten in subclasses to allow individual widgets to
34+
* change the visual cue.
35+
* @note added in QGIS 2.16
36+
*/
37+
void updateConstraintWidgetStatus();
2438
};

‎src/app/qgsattributetypedialog.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ QgsAttributeTypeDialog::QgsAttributeTypeDialog( QgsVectorLayer *vl, int fieldIdx
7171

7272
QSettings settings;
7373
restoreGeometry( settings.value( "/Windows/QgsAttributeTypeDialog/geometry" ).toByteArray() );
74+
75+
constraintExpression->setLayer( vl );
7476
}
7577

7678
QgsAttributeTypeDialog::~QgsAttributeTypeDialog()
@@ -183,6 +185,16 @@ bool QgsAttributeTypeDialog::notNull() const
183185
return notNullCheckBox->isChecked();
184186
}
185187

188+
void QgsAttributeTypeDialog::setConstraint( const QString &str )
189+
{
190+
constraintExpression->setField( str );
191+
}
192+
193+
QString QgsAttributeTypeDialog::constraint() const
194+
{
195+
return constraintExpression->asExpression();
196+
}
197+
186198
void QgsAttributeTypeDialog::setFieldEditable( bool editable )
187199
{
188200
isFieldEditableCheckBox->setChecked( editable );

‎src/app/qgsattributetypedialog.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,18 @@ class APP_EXPORT QgsAttributeTypeDialog: public QDialog, private Ui::QgsAttribut
9494
*/
9595
bool notNull() const;
9696

97+
/**
98+
* Getter for the constraint expression
99+
* @note added in QGIS 2.16
100+
*/
101+
QString constraint() const;
102+
103+
/**
104+
* Setter for the constraint expression
105+
* @note added in QGIS 2.16
106+
*/
107+
void setConstraint( const QString &str );
108+
97109
private slots:
98110
/**
99111
* Slot to handle change of index in combobox to select correct page

‎src/app/qgsfieldsproperties.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,7 @@ void QgsFieldsProperties::attributeTypeDialog()
528528
attributeTypeDialog.setFieldEditable( cfg.mEditable );
529529
attributeTypeDialog.setLabelOnTop( cfg.mLabelOnTop );
530530
attributeTypeDialog.setNotNull( cfg.mNotNull );
531+
attributeTypeDialog.setConstraint( cfg.mConstraint );
531532

532533
attributeTypeDialog.setWidgetV2Config( cfg.mEditorWidgetV2Config );
533534
attributeTypeDialog.setWidgetV2Type( cfg.mEditorWidgetV2Type );
@@ -538,6 +539,7 @@ void QgsFieldsProperties::attributeTypeDialog()
538539
cfg.mEditable = attributeTypeDialog.fieldEditable();
539540
cfg.mLabelOnTop = attributeTypeDialog.labelOnTop();
540541
cfg.mNotNull = attributeTypeDialog.notNull();
542+
cfg.mConstraint = attributeTypeDialog.constraint();
541543

542544
cfg.mEditorWidgetV2Type = attributeTypeDialog.editorWidgetV2Type();
543545
cfg.mEditorWidgetV2Config = attributeTypeDialog.editorWidgetV2Config();
@@ -911,6 +913,7 @@ void QgsFieldsProperties::apply()
911913
mLayer->editFormConfig()->setReadOnly( i, !cfg.mEditable );
912914
mLayer->editFormConfig()->setLabelOnTop( i, cfg.mLabelOnTop );
913915
mLayer->editFormConfig()->setNotNull( i, cfg.mNotNull );
916+
mLayer->editFormConfig()->setConstraint( i, cfg.mConstraint );
914917

915918
mLayer->editFormConfig()->setWidgetType( idx, cfg.mEditorWidgetV2Type );
916919
mLayer->editFormConfig()->setWidgetConfig( idx, cfg.mEditorWidgetV2Config );
@@ -990,6 +993,7 @@ QgsFieldsProperties::FieldConfig::FieldConfig( QgsVectorLayer* layer, int idx )
990993
&& layer->fields().fieldOrigin( idx ) != QgsFields::OriginExpression;
991994
mLabelOnTop = layer->editFormConfig()->labelOnTop( idx );
992995
mNotNull = layer->editFormConfig()->notNull( idx );
996+
mConstraint = layer->editFormConfig()->constraint( idx );
993997
mEditorWidgetV2Type = layer->editFormConfig()->widgetType( idx );
994998
mEditorWidgetV2Config = layer->editFormConfig()->widgetConfig( idx );
995999

‎src/app/qgsfieldsproperties.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ class APP_EXPORT QgsFieldsProperties : public QWidget, private Ui_QgsFieldsPrope
9393
bool mEditableEnabled;
9494
bool mLabelOnTop;
9595
bool mNotNull;
96+
QString mConstraint;
9697
QPushButton* mButton;
9798
QString mEditorWidgetV2Type;
9899
QMap<QString, QVariant> mEditorWidgetV2Config;

‎src/core/qgseditformconfig.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,22 @@ bool QgsEditFormConfig::labelOnTop( int idx ) const
119119
return false;
120120
}
121121

122+
QString QgsEditFormConfig::constraint( int idx ) const
123+
{
124+
QString expr = "";
125+
126+
if ( idx >= 0 && idx < mFields.count() )
127+
expr = mConstraints.value( mFields.at( idx ).name(), "" );
128+
129+
return expr;
130+
}
131+
132+
void QgsEditFormConfig::setConstraint( int idx, const QString& str )
133+
{
134+
if ( idx >= 0 && idx < mFields.count() )
135+
mConstraints[ mFields.at( idx ).name()] = str;
136+
}
137+
122138
bool QgsEditFormConfig::notNull( int idx ) const
123139
{
124140
if ( idx >= 0 && idx < mFields.count() )

‎src/core/qgseditformconfig.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,22 @@ class CORE_EXPORT QgsEditFormConfig : public QObject
512512
*/
513513
void setReadOnly( int idx, bool readOnly = true );
514514

515+
/**
516+
* Returns the constraint expression of a specific field
517+
* @param idx The index of the field
518+
* @return the expression
519+
* @note added in QGIS 2.16
520+
*/
521+
QString constraint( int idx ) const;
522+
523+
/**
524+
* Set the constraint expression for a specific field
525+
* @param idx the field index
526+
* @param str the constraint expression
527+
* @note added in QGIS 2.16
528+
*/
529+
void setConstraint( int idx, const QString& str );
530+
515531
/**
516532
* Returns if the field at fieldidx should be treated as NOT NULL value
517533
*/
@@ -640,6 +656,7 @@ class CORE_EXPORT QgsEditFormConfig : public QObject
640656
/** Map that stores the tab for attributes in the edit form. Key is the tab order and value the tab name*/
641657
QList< TabData > mTabs;
642658

659+
QMap< QString, QString> mConstraints;
643660
QMap< QString, bool> mFieldEditables;
644661
QMap< QString, bool> mLabelOnTop;
645662
QMap< QString, bool> mNotNull;

‎src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,8 @@ void QgsEditorWidgetRegistry::readMapLayer( QgsMapLayer* mapLayer, const QDomEle
252252
vectorLayer->editFormConfig()->setReadOnly( idx, ewv2CfgElem.attribute( "fieldEditable", "1" ) != "1" );
253253
vectorLayer->editFormConfig()->setLabelOnTop( idx, ewv2CfgElem.attribute( "labelOnTop", "0" ) == "1" );
254254
vectorLayer->editFormConfig()->setNotNull( idx, ewv2CfgElem.attribute( "notNull", "0" ) == "1" );
255+
vectorLayer->editFormConfig()->setConstraint( idx, ewv2CfgElem.attribute( "constraint", "" ) );
256+
255257
vectorLayer->editFormConfig()->setWidgetConfig( idx, cfg );
256258
}
257259
else
@@ -309,6 +311,7 @@ void QgsEditorWidgetRegistry::writeMapLayer( QgsMapLayer* mapLayer, QDomElement&
309311
ewv2CfgElem.setAttribute( "fieldEditable", !vectorLayer->editFormConfig()->readOnly( idx ) );
310312
ewv2CfgElem.setAttribute( "labelOnTop", vectorLayer->editFormConfig()->labelOnTop( idx ) );
311313
ewv2CfgElem.setAttribute( "notNull", vectorLayer->editFormConfig()->notNull( idx ) );
314+
ewv2CfgElem.setAttribute( "constraint", vectorLayer->editFormConfig()->constraint( idx ) );
312315

313316
mWidgetFactories[widgetType]->writeConfig( vectorLayer->editFormConfig()->widgetConfig( idx ), ewv2CfgElem, doc, vectorLayer, idx );
314317

‎src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@
2222

2323
QgsEditorWidgetWrapper::QgsEditorWidgetWrapper( QgsVectorLayer* vl, int fieldIdx, QWidget* editor, QWidget* parent )
2424
: QgsWidgetWrapper( vl, editor, parent )
25+
, mValidConstraint( true )
2526
, mFieldIdx( fieldIdx )
2627
{
27-
connect( this, SIGNAL( valueChanged( QVariant ) ), this, SLOT( onValueChanged( QVariant ) ) );
2828
}
2929

3030
int QgsEditorWidgetWrapper::fieldIdx() const
@@ -61,8 +61,8 @@ void QgsEditorWidgetWrapper::setEnabled( bool enabled )
6161

6262
void QgsEditorWidgetWrapper::setFeature( const QgsFeature& feature )
6363
{
64+
mFeature = feature;
6465
setValue( feature.attribute( mFieldIdx ) );
65-
onValueChanged( value() );
6666
}
6767

6868
void QgsEditorWidgetWrapper::valueChanged( const QString& value )
@@ -95,27 +95,69 @@ void QgsEditorWidgetWrapper::valueChanged()
9595
emit valueChanged( value() );
9696
}
9797

98-
void QgsEditorWidgetWrapper::updateConstraintsOk( bool constraintStatus )
98+
void QgsEditorWidgetWrapper::updateConstraintWidgetStatus()
9999
{
100-
if ( constraintStatus )
101-
{
100+
if ( mValidConstraint )
102101
widget()->setStyleSheet( "" );
103-
}
104102
else
105-
{
106-
widget()->setStyleSheet( "QWidget{ background-color: '#dd7777': }" );
107-
}
103+
widget()->setStyleSheet( "background-color: #dd7777;" );
108104
}
109105

110-
void QgsEditorWidgetWrapper::onValueChanged( const QVariant& value )
106+
void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft )
111107
{
108+
bool toEmit( false );
109+
QString errStr( "predicate is True" );
110+
QString expression = layer()->editFormConfig()->constraint( mFieldIdx );
111+
QVariant value = ft.attribute( mFieldIdx );
112+
113+
if ( ! expression.isEmpty() )
114+
{
115+
QgsExpressionContext context =
116+
QgsExpressionContextUtils::createFeatureBasedContext( ft, *ft.fields() );
117+
118+
context.setFeature( ft );
119+
QgsExpression expr( expression );
120+
121+
mValidConstraint = expr.evaluate( &context ).toBool();
122+
123+
if ( expr.hasParserError() )
124+
errStr = expr.parserErrorString();
125+
else if ( expr.hasEvalError() )
126+
errStr = expr.evalErrorString();
127+
else if ( ! mValidConstraint )
128+
errStr = "predicate is False";
129+
130+
toEmit = true;
131+
}
132+
else
133+
mValidConstraint = true;
134+
112135
if ( layer()->editFormConfig()->notNull( mFieldIdx ) )
113136
{
114-
if ( value.isNull() != mIsNull )
137+
if ( !expression.isEmpty() )
115138
{
116-
updateConstraintsOk( value.isNull() );
117-
emit constraintStatusChanged( "NotNull", !value.isNull() );
118-
mIsNull = value.isNull();
139+
QString fieldName = ft.fields()->field( mFieldIdx ).name();
140+
expression = "( " + expression + " ) AND ( " + fieldName + " IS NOT NULL)";
119141
}
142+
else
143+
expression = "NotNull";
144+
145+
mValidConstraint = mValidConstraint && !value.isNull();
146+
147+
if ( value.isNull() )
148+
errStr = "predicate is False";
149+
150+
toEmit = true;
120151
}
152+
153+
if ( toEmit )
154+
{
155+
updateConstraintWidgetStatus();
156+
emit constraintStatusChanged( expression, errStr, mValidConstraint );
157+
}
158+
}
159+
160+
bool QgsEditorWidgetWrapper::isValidConstraint() const
161+
{
162+
return mValidConstraint;
121163
}

‎src/gui/editorwidgets/core/qgseditorwidgetwrapper.h

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,21 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
110110
*/
111111
virtual void showIndeterminateState() {}
112112

113+
/**
114+
* Update constraint.
115+
* @param featureContext the feature to use to evaluate the constraint
116+
* @note added in QGIS 2.16
117+
*/
118+
void updateConstraint( const QgsFeature &featureContext );
119+
120+
/**
121+
* Get the current constraint status.
122+
* @return true if the constraint is valid or if there's not constraint,
123+
* false otherwise
124+
* @note added in QGIS 2.16
125+
*/
126+
bool isValidConstraint() const;
127+
113128
signals:
114129
/**
115130
* Emit this signal, whenever the value changed.
@@ -119,11 +134,13 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
119134
void valueChanged( const QVariant& value );
120135

121136
/**
137+
* Emit this signal when the constraint status changed.
122138
* @brief constraintStatusChanged
123-
* @param constraint
139+
* @param constraint represented as a string
140+
* @param err the error represented as a string. Empty if none.
124141
* @param status
125142
*/
126-
void constraintStatusChanged( const QString& constraint, bool status );
143+
void constraintStatusChanged( const QString& constraint, const QString& err, bool status );
127144

128145
public slots:
129146
/**
@@ -192,7 +209,7 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
192209
*/
193210
void valueChanged();
194211

195-
private:
212+
protected:
196213
/**
197214
* This should update the widget with a visual cue if a constraint status
198215
* changed.
@@ -202,18 +219,15 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
202219
*
203220
* This can be overwritten in subclasses to allow individual widgets to
204221
* change the visual cue.
222+
* @note added in QGIS 2.16
205223
*/
206-
virtual void updateConstraintsOk( bool constraintStatus );
224+
virtual void updateConstraintWidgetStatus();
207225

208-
private slots:
209-
/**
210-
* @brief mFieldIdx
211-
*/
212-
void onValueChanged( const QVariant& value );
226+
bool mValidConstraint;
213227

214228
private:
215229
int mFieldIdx;
216-
bool mIsNull;
230+
QgsFeature mFeature;
217231
};
218232

219233
// We'll use this class inside a QVariant in the widgets properties

‎src/gui/editorwidgets/qgscolorwidgetwrapper.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,8 @@ void QgsColorWidgetWrapper::setValue( const QVariant& value )
7777
if ( mColorButton )
7878
mColorButton->setColor( !value.isNull() ? QColor( value.toString() ) : QColor() );
7979
}
80+
81+
void QgsColorWidgetWrapper::updateConstraintWidgetStatus()
82+
{
83+
// nothing
84+
}

‎src/gui/editorwidgets/qgscolorwidgetwrapper.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ class GUI_EXPORT QgsColorWidgetWrapper : public QgsEditorWidgetWrapper
4646
void setValue( const QVariant& value ) override;
4747

4848
private:
49+
void updateConstraintWidgetStatus() override;
50+
4951
QgsColorButtonV2* mColorButton;
5052
};
5153

‎src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.cpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,9 @@ void QgsExternalResourceWidgetWrapper::initWidget( QWidget* editor )
9595
{
9696
fle->setNullValue( QSettings().value( "qgis/nullValue", "NULL" ).toString() );
9797
}
98-
connect( mLineEdit, SIGNAL( textChanged( QString ) ), this, SLOT( valueChanged( QString ) ) );
9998
}
99+
else
100+
mLineEdit = editor->findChild<QLineEdit*>();
100101

101102
if ( mQgsWidget )
102103
{
@@ -138,6 +139,10 @@ void QgsExternalResourceWidgetWrapper::initWidget( QWidget* editor )
138139
mQgsWidget->fileWidget()->setFilter( config( "FileWidgetFilter" ).toString() );
139140
}
140141
}
142+
143+
if ( mLineEdit )
144+
connect( mLineEdit, SIGNAL( textChanged( QString ) ), this, SLOT( valueChanged( QString ) ) );
145+
141146
}
142147

143148
void QgsExternalResourceWidgetWrapper::setValue( const QVariant& value )
@@ -182,3 +187,14 @@ void QgsExternalResourceWidgetWrapper::setEnabled( bool enabled )
182187
if ( mQgsWidget )
183188
mQgsWidget->setReadOnly( !enabled );
184189
}
190+
191+
void QgsExternalResourceWidgetWrapper::updateConstraintWidgetStatus()
192+
{
193+
if ( mLineEdit )
194+
{
195+
if ( mValidConstraint )
196+
mLineEdit->setStyleSheet( "" );
197+
else
198+
mLineEdit->setStyleSheet( "QgsFilterLineEdit { background-color: #dd7777; }" );
199+
}
200+
}

‎src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ class GUI_EXPORT QgsExternalResourceWidgetWrapper : public QgsEditorWidgetWrappe
5454
void setEnabled( bool enabled ) override;
5555

5656
private:
57+
void updateConstraintWidgetStatus() override;
58+
5759
QLineEdit* mLineEdit;
5860
QLabel* mLabel;
5961
QgsExternalResourceWidget* mQgsWidget;

‎src/gui/editorwidgets/qgsfilenamewidgetwrapper.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,16 @@ void QgsFileNameWidgetWrapper::selectFileName()
151151
if ( mLabel )
152152
mLineEdit->setText( fileName );
153153
}
154+
155+
void QgsFileNameWidgetWrapper::updateConstraintWidgetStatus()
156+
{
157+
if ( mLineEdit )
158+
{
159+
if ( mValidConstraint )
160+
mLineEdit->setStyleSheet( "" );
161+
else
162+
{
163+
mLineEdit->setStyleSheet( "QgsFilterLineEdit { background-color: #dd7777; }" );
164+
}
165+
}
166+
}

‎src/gui/editorwidgets/qgsfilenamewidgetwrapper.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ class GUI_EXPORT QgsFileNameWidgetWrapper : public QgsEditorWidgetWrapper
5151
void setValue( const QVariant& value ) override;
5252

5353
private:
54+
void updateConstraintWidgetStatus() override;
55+
5456
QLineEdit* mLineEdit;
5557
QPushButton* mPushButton;
5658
QLabel* mLabel;

‎src/gui/editorwidgets/qgsphotowidgetwrapper.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,3 +266,16 @@ void QgsPhotoWidgetWrapper::setEnabled( bool enabled )
266266
if ( mButton )
267267
mButton->setEnabled( enabled );
268268
}
269+
270+
void QgsPhotoWidgetWrapper::updateConstraintWidgetStatus()
271+
{
272+
if ( mLineEdit )
273+
{
274+
if ( mValidConstraint )
275+
mLineEdit->setStyleSheet( "" );
276+
else
277+
{
278+
mLineEdit->setStyleSheet( "QgsFilterLineEdit { background-color: #dd7777; }" );
279+
}
280+
}
281+
}

‎src/gui/editorwidgets/qgsphotowidgetwrapper.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ class GUI_EXPORT QgsPhotoWidgetWrapper : public QgsEditorWidgetWrapper
6565
void loadPixmap( const QString& fileName );
6666

6767
private:
68+
void updateConstraintWidgetStatus() override;
69+
6870
//! This label is used as a container to display the picture
6971
QLabel* mPhotoLabel;
7072
//! This label is used as a container to display a picture that scales with the dialog layout.

‎src/gui/editorwidgets/qgsrelationreferencewidgetwrapper.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,14 @@ void QgsRelationReferenceWidgetWrapper::foreignKeyChanged( QVariant value )
139139
}
140140
emit valueChanged( value );
141141
}
142+
143+
void QgsRelationReferenceWidgetWrapper::updateConstraintWidgetStatus()
144+
{
145+
if ( mWidget )
146+
{
147+
if ( mValidConstraint )
148+
mWidget->setStyleSheet( "" );
149+
else
150+
mWidget->setStyleSheet( ".QComboBox { background-color: #dd7777; }" );
151+
}
152+
}

‎src/gui/editorwidgets/qgsrelationreferencewidgetwrapper.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,20 @@ class GUI_EXPORT QgsRelationReferenceWidgetWrapper : public QgsEditorWidgetWrapp
5959
private slots:
6060
void foreignKeyChanged( QVariant value );
6161

62+
protected:
63+
/**
64+
* This should update the widget with a visual cue if a constraint status
65+
* changed.
66+
*
67+
* By default a stylesheet will be applied on the widget that changes the
68+
* background color to red.
69+
*
70+
* This can be overwritten in subclasses to allow individual widgets to
71+
* change the visual cue.
72+
* @note added in QGIS 2.16
73+
*/
74+
void updateConstraintWidgetStatus() override;
75+
6276
private:
6377
QgsRelationReferenceWidget* mWidget;
6478
QgsMapCanvas* mCanvas;

‎src/gui/editorwidgets/qgswebviewwidgetwrapper.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,3 +188,16 @@ void QgsWebViewWidgetWrapper::selectFileName()
188188
if ( mLineEdit )
189189
mLineEdit->setText( filePath );
190190
}
191+
192+
void QgsWebViewWidgetWrapper::updateConstraintWidgetStatus()
193+
{
194+
if ( mLineEdit )
195+
{
196+
if ( mValidConstraint )
197+
mLineEdit->setStyleSheet( "" );
198+
else
199+
{
200+
mLineEdit->setStyleSheet( "QgsFilterLineEdit { background-color: #dd7777; }" );
201+
}
202+
}
203+
}

‎src/gui/editorwidgets/qgswebviewwidgetwrapper.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ class GUI_EXPORT QgsWebViewWidgetWrapper : public QgsEditorWidgetWrapper
5353
void selectFileName();
5454

5555
private:
56+
void updateConstraintWidgetStatus() override;
57+
5658
//! This label is used as a container to display the picture
5759
QWebView* mWebView;
5860
//! The line edit containing the path to the picture

‎src/gui/qgsattributeform.cpp

Lines changed: 184 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ int QgsAttributeForm::sFormCounter = 0;
4949

5050
QgsAttributeForm::QgsAttributeForm( QgsVectorLayer* vl, const QgsFeature &feature, const QgsAttributeEditorContext &context, QWidget* parent )
5151
: QWidget( parent )
52+
, mInvalidConstraintMessageBarItem( nullptr )
53+
, mFieldNotInitializedMessageBarItem( nullptr )
5254
, mLayer( vl )
5355
, mMessageBar( nullptr )
5456
, mMultiEditUnsavedMessageBarItem( nullptr )
@@ -79,6 +81,10 @@ QgsAttributeForm::QgsAttributeForm( QgsVectorLayer* vl, const QgsFeature &featur
7981
connect( vl, SIGNAL( beforeAddingExpressionField( QString ) ), this, SLOT( preventFeatureRefresh() ) );
8082
connect( vl, SIGNAL( beforeRemovingExpressionField( int ) ), this, SLOT( preventFeatureRefresh() ) );
8183
connect( vl, SIGNAL( selectionChanged() ), this, SLOT( layerSelectionChanged() ) );
84+
85+
// constraints management
86+
displayNullFieldsMessage();
87+
updateAllConstaints();
8288
}
8389

8490
QgsAttributeForm::~QgsAttributeForm()
@@ -688,10 +694,137 @@ void QgsAttributeForm::onAttributeChanged( const QVariant& value )
688694
}
689695
}
690696

697+
updateConstraints( eww );
691698

699+
// emit
692700
emit attributeChanged( eww->field().name(), value );
693701
}
694702

703+
void QgsAttributeForm::updateAllConstaints()
704+
{
705+
Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
706+
{
707+
QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
708+
if ( eww )
709+
updateConstraints( eww );
710+
}
711+
}
712+
713+
void QgsAttributeForm::updateConstraints( QgsEditorWidgetWrapper *eww )
714+
{
715+
// get the current feature set in the form
716+
QgsFeature ft;
717+
if ( currentFormFeature( ft ) )
718+
{
719+
// update eww constraint
720+
eww->updateConstraint( ft );
721+
722+
// update eww dependencies constraint
723+
QList<QgsEditorWidgetWrapper*> deps;
724+
constraintDependencies( eww, deps );
725+
726+
Q_FOREACH ( QgsEditorWidgetWrapper* depsEww, deps )
727+
depsEww->updateConstraint( ft );
728+
729+
// sync ok button status
730+
synchronizeEnabledState();
731+
}
732+
}
733+
734+
bool QgsAttributeForm::currentFormFeature( QgsFeature &feature )
735+
{
736+
bool rc = true;
737+
feature = QgsFeature( mFeature );
738+
QgsAttributes src = feature.attributes();
739+
QgsAttributes dst = feature.attributes();
740+
741+
Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
742+
{
743+
QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
744+
if ( eww && dst.count() > eww->fieldIdx() )
745+
{
746+
QVariant dstVar = dst.at( eww->fieldIdx() );
747+
QVariant srcVar = eww->value();
748+
// need to check dstVar.isNull() != srcVar.isNull()
749+
// otherwise if dstVar=NULL and scrVar=0, then dstVar = srcVar
750+
if (( dstVar != srcVar || dstVar.isNull() != srcVar.isNull() ) && srcVar.isValid() && !mLayer->editFormConfig()->readOnly( eww->fieldIdx() ) )
751+
dst[eww->fieldIdx()] = srcVar;
752+
}
753+
else
754+
{
755+
rc = false;
756+
break;
757+
}
758+
}
759+
760+
feature.setAttributes( dst );
761+
762+
return rc;
763+
}
764+
765+
void QgsAttributeForm::displayNullFieldsMessage()
766+
{
767+
QStringList notInitializedFields;
768+
Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
769+
{
770+
QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
771+
if ( eww )
772+
{
773+
if ( mFeature.attribute( eww->fieldIdx() ).isNull() )
774+
notInitializedFields.append( eww->field().name() );
775+
}
776+
}
777+
778+
if ( ! notInitializedFields.isEmpty() )
779+
{
780+
mFieldNotInitializedMessageBarItem =
781+
new QgsMessageBarItem( tr( "Some fields are NULL: " ),
782+
notInitializedFields.join( ", " ),
783+
QgsMessageBar::INFO );
784+
mMessageBar->pushItem( mFieldNotInitializedMessageBarItem );
785+
}
786+
787+
}
788+
789+
void QgsAttributeForm::clearInvalidConstraintsMessage()
790+
{
791+
if ( mInvalidConstraintMessageBarItem != nullptr )
792+
{
793+
mMessageBar->popWidget( mInvalidConstraintMessageBarItem );
794+
mInvalidConstraintMessageBarItem = nullptr;
795+
}
796+
}
797+
798+
void QgsAttributeForm::displayInvalidConstraintMessage( const QStringList &f )
799+
{
800+
clearInvalidConstraintsMessage();
801+
802+
mInvalidConstraintMessageBarItem =
803+
new QgsMessageBarItem( tr( "Invalid fields:" ),
804+
f.join( ", " ), QgsMessageBar::WARNING );
805+
mMessageBar->pushItem( mInvalidConstraintMessageBarItem );
806+
}
807+
808+
bool QgsAttributeForm::currentFormValidConstraints( QStringList &invalidFields )
809+
{
810+
bool valid( true );
811+
812+
Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
813+
{
814+
QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
815+
if ( eww )
816+
{
817+
if ( ! eww->isValidConstraint() )
818+
{
819+
invalidFields.append( eww->field().name() );
820+
valid = false; // continue to get all invalif fields
821+
}
822+
}
823+
}
824+
825+
return valid;
826+
}
827+
695828
void QgsAttributeForm::onAttributeAdded( int idx )
696829
{
697830
mPreventFeatureRefresh = false;
@@ -749,18 +882,19 @@ void QgsAttributeForm::onUpdatedFields()
749882
setFeature( mFeature );
750883
}
751884

752-
void QgsAttributeForm::onConstraintStatusChanged( const QString& constraint, bool ok )
885+
void QgsAttributeForm::onConstraintStatusChanged( const QString& constraint,
886+
const QString& err, bool ok )
753887
{
754-
Q_UNUSED( constraint )
755-
756-
757888
QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( sender() );
758889
Q_ASSERT( eww );
759890

760891
QLabel* buddy = mBuddyMap.value( eww->widget() );
761892

762893
if ( buddy )
763894
{
895+
QString tooltip = "Expression: " + constraint + "\n" + "Constraint: " + err;
896+
buddy->setToolTip( tooltip );
897+
764898
if ( !buddy->property( "originalText" ).isValid() )
765899
buddy->setProperty( "originalText", buddy->text() );
766900

@@ -779,6 +913,38 @@ void QgsAttributeForm::onConstraintStatusChanged( const QString& constraint, boo
779913
}
780914
}
781915

916+
void QgsAttributeForm::constraintDependencies( QgsEditorWidgetWrapper *w,
917+
QList<QgsEditorWidgetWrapper*> &wDeps )
918+
{
919+
QString name = w->field().name();
920+
921+
// for each widget in the current form
922+
Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
923+
{
924+
// get the wrapper
925+
QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
926+
if ( eww )
927+
{
928+
// compare name to not compare w to itself
929+
QString ewwName = eww->field().name();
930+
if ( name != ewwName )
931+
{
932+
// get expression and referencedColumns
933+
QgsExpression expr = eww->layer()->editFormConfig()->constraint( eww->fieldIdx() );
934+
935+
Q_FOREACH ( const QString& colName, expr.referencedColumns() )
936+
{
937+
if ( name == colName )
938+
{
939+
wDeps.append( eww );
940+
break;
941+
}
942+
}
943+
}
944+
}
945+
}
946+
}
947+
782948
void QgsAttributeForm::preventFeatureRefresh()
783949
{
784950
mPreventFeatureRefresh = true;
@@ -817,6 +983,18 @@ void QgsAttributeForm::synchronizeEnabledState()
817983
ww->setEnabled( isEditable && fieldEditable );
818984
}
819985

986+
// push a message and disable the OK button if constraints are invalid
987+
clearInvalidConstraintsMessage();
988+
989+
QStringList invalidFields;
990+
bool validConstraint = currentFormValidConstraints( invalidFields );
991+
992+
if ( ! validConstraint )
993+
displayInvalidConstraintMessage( invalidFields );
994+
995+
isEditable = isEditable & validConstraint;
996+
997+
// change ok button status
820998
QPushButton* okButton = mButtonBox->button( QDialogButtonBox::Ok );
821999
if ( okButton )
8221000
okButton->setEnabled( isEditable );
@@ -980,7 +1158,7 @@ void QgsAttributeForm::init()
9801158
w = new QLabel( QString( "<p style=\"color: red; font-style: italic;\">Failed to create widget with type '%1'</p>" ).arg( widgetType ) );
9811159
}
9821160

983-
l->setBuddy( w );
1161+
l->setBuddy( eww->widget() );
9841162

9851163
if ( w )
9861164
w->setObjectName( field.name() );
@@ -1440,12 +1618,11 @@ void QgsAttributeForm::afterWidgetInit()
14401618
}
14411619

14421620
connect( eww, SIGNAL( valueChanged( const QVariant& ) ), this, SLOT( onAttributeChanged( const QVariant& ) ) );
1443-
connect( eww, SIGNAL( constraintStatusChanged( QString, bool ) ), this, SLOT( onConstraintStatusChanged( QString, bool ) ) );
1621+
connect( eww, SIGNAL( constraintStatusChanged( QString, QString, bool ) ), this, SLOT( onConstraintStatusChanged( QString, QString, bool ) ) );
14441622
}
14451623
}
14461624

14471625
// Update buddy widget list
1448-
14491626
mBuddyMap.clear();
14501627
QList<QLabel*> labels = findChildren<QLabel*>();
14511628

‎src/gui/qgsattributeform.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ class GUI_EXPORT QgsAttributeForm : public QWidget
234234
void onAttributeAdded( int idx );
235235
void onAttributeDeleted( int idx );
236236
void onUpdatedFields();
237-
void onConstraintStatusChanged( const QString& constraint, bool ok );
237+
void onConstraintStatusChanged( const QString& constraint, const QString& err, bool ok );
238238

239239
void preventFeatureRefresh();
240240
void synchronizeEnabledState();
@@ -298,6 +298,18 @@ class GUI_EXPORT QgsAttributeForm : public QWidget
298298

299299
QString createFilterExpression() const;
300300

301+
//! constraints management
302+
void updateAllConstaints();
303+
void updateConstraints( QgsEditorWidgetWrapper *w );
304+
bool currentFormFeature( QgsFeature &feature );
305+
bool currentFormValidConstraints( QStringList &invalidFields );
306+
void constraintDependencies( QgsEditorWidgetWrapper *w, QList<QgsEditorWidgetWrapper*> &wDeps );
307+
void clearInvalidConstraintsMessage();
308+
void displayInvalidConstraintMessage( const QStringList &invalidFields );
309+
void displayNullFieldsMessage();
310+
QgsMessageBarItem *mInvalidConstraintMessageBarItem;
311+
QgsMessageBarItem *mFieldNotInitializedMessageBarItem;
312+
301313
QgsVectorLayer* mLayer;
302314
QgsFeature mFeature;
303315
QgsMessageBar* mMessageBar;
@@ -336,6 +348,7 @@ class GUI_EXPORT QgsAttributeForm : public QWidget
336348
QMap<QWidget*, QLabel*> mBuddyMap;
337349

338350
friend class TestQgsDualView;
351+
friend class TestQgsAttributeForm;
339352
};
340353

341354
#endif // QGSATTRIBUTEFORM_H

‎src/ui/qgsattributetypeedit.ui

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@
1414
<string>Edit Widget Properties</string>
1515
</property>
1616
<layout class="QGridLayout" name="gridLayout">
17-
<item row="3" column="1">
18-
<widget class="QStackedWidget" name="stackedWidget"/>
19-
</item>
2017
<item row="0" column="1">
2118
<widget class="QCheckBox" name="isFieldEditableCheckBox">
2219
<property name="text">
@@ -37,10 +34,10 @@
3734
</property>
3835
</widget>
3936
</item>
40-
<item row="0" column="0" rowspan="8">
37+
<item row="0" column="0" rowspan="11">
4138
<widget class="QListWidget" name="selectionListWidget"/>
4239
</item>
43-
<item row="7" column="1">
40+
<item row="10" column="1">
4441
<widget class="QDialogButtonBox" name="buttonBox">
4542
<property name="orientation">
4643
<enum>Qt::Horizontal</enum>
@@ -50,15 +47,42 @@
5047
</property>
5148
</widget>
5249
</item>
50+
<item row="6" column="1">
51+
<widget class="QStackedWidget" name="stackedWidget"/>
52+
</item>
5353
<item row="2" column="1">
5454
<widget class="QCheckBox" name="notNullCheckBox">
5555
<property name="text">
5656
<string>Not Null</string>
5757
</property>
5858
</widget>
5959
</item>
60+
<item row="4" column="1">
61+
<layout class="QHBoxLayout" name="horizontalLayout_2">
62+
<property name="topMargin">
63+
<number>0</number>
64+
</property>
65+
<item>
66+
<widget class="QLabel" name="label">
67+
<property name="text">
68+
<string>Constraint</string>
69+
</property>
70+
</widget>
71+
</item>
72+
<item>
73+
<widget class="QgsFieldExpressionWidget" name="constraintExpression" native="true"/>
74+
</item>
75+
</layout>
76+
</item>
6077
</layout>
6178
</widget>
79+
<customwidgets>
80+
<customwidget>
81+
<class>QgsFieldExpressionWidget</class>
82+
<extends>QWidget</extends>
83+
<header>qgsfieldexpressionwidget.h</header>
84+
</customwidget>
85+
</customwidgets>
6286
<tabstops>
6387
<tabstop>selectionListWidget</tabstop>
6488
<tabstop>isFieldEditableCheckBox</tabstop>

‎tests/src/gui/CMakeLists.txt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ SET (util_SRCS)
55
#####################################################
66
# Don't forget to include output directory, otherwise
77
# the UI file won't be wrapped!
8-
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}
8+
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}
99
${CMAKE_CURRENT_BINARY_DIR}
1010
${CMAKE_CURRENT_BINARY_DIR}/../../../src/ui
1111
${CMAKE_CURRENT_SOURCE_DIR}/../core #for render checker class
@@ -62,7 +62,7 @@ ENDIF (APPLE)
6262
#qtests in the executable file list as the moc is
6363
#directly included in the sources
6464
#and should not be compiled twice. Trying to include
65-
#them in will cause an error at build time
65+
#them in will cause an error at build time
6666
#############################################################
6767
# Tests:
6868

@@ -76,7 +76,7 @@ ENDIF (APPLE)
7676
#ADD_EXECUTABLE(qgis_quickprinttest ${qgis_quickprinttest_SRCS})
7777
#ADD_DEPENDENCIES(qgis_quickprinttest qgis_quickprinttestmoc)
7878
#TARGET_LINK_LIBRARIES(qgis_quickprinttest ${QT_LIBRARIES} qgis_core qgis_gui)
79-
#SET_TARGET_PROPERTIES(qgis_quickprinttest
79+
#SET_TARGET_PROPERTIES(qgis_quickprinttest
8080
# PROPERTIES INSTALL_RPATH ${QGIS_LIB_DIR}
8181
# INSTALL_RPATH_USE_LINK_PATH true)
8282
#IF (APPLE)
@@ -128,6 +128,7 @@ ADD_QGIS_TEST(zoomtest testqgsmaptoolzoom.cpp)
128128
#ADD_QGIS_TEST(histogramtest testqgsrasterhistogram.cpp)
129129
ADD_QGIS_TEST(doublespinbox testqgsdoublespinbox.cpp)
130130
ADD_QGIS_TEST(dualviewtest testqgsdualview.cpp)
131+
ADD_QGIS_TEST(attributeformtest testqgsattributeform.cpp)
131132
ADD_QGIS_TEST(fieldexpressionwidget testqgsfieldexpressionwidget.cpp)
132133
ADD_QGIS_TEST(filewidget testqgsfilewidget.cpp)
133134
ADD_QGIS_TEST(focuswatcher testqgsfocuswatcher.cpp)
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
/***************************************************************************
2+
testqgsdualview.cpp
3+
--------------------------------------
4+
Date : 13 05 2016
5+
Copyright : (C) 2016 Paul Blottiere
6+
Email : paul.blottiere@oslandia.com
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+
17+
#include <QtTest/QtTest>
18+
#include <QPushButton>
19+
20+
#include <editorwidgets/core/qgseditorwidgetregistry.h>
21+
#include "qgsattributeform.h"
22+
#include <qgsapplication.h>
23+
#include <qgsvectorlayer.h>
24+
#include "qgsvectordataprovider.h"
25+
#include <qgsfeature.h>
26+
27+
class TestQgsAttributeForm : public QObject
28+
{
29+
Q_OBJECT
30+
public:
31+
TestQgsAttributeForm() {}
32+
33+
private slots:
34+
void initTestCase(); // will be called before the first testfunction is executed.
35+
void cleanupTestCase(); // will be called after the last testfunction was executed.
36+
void init(); // will be called before each testfunction is executed.
37+
void cleanup(); // will be called after every testfunction.
38+
39+
void testFieldConstraint();
40+
void testFieldMultiConstraints();
41+
void testOKButtonStatus();
42+
};
43+
44+
void TestQgsAttributeForm::initTestCase()
45+
{
46+
QgsApplication::init();
47+
QgsApplication::initQgis();
48+
QgsEditorWidgetRegistry::initEditors();
49+
}
50+
51+
void TestQgsAttributeForm::cleanupTestCase()
52+
{
53+
QgsApplication::exitQgis();
54+
}
55+
56+
void TestQgsAttributeForm::init()
57+
{
58+
}
59+
60+
void TestQgsAttributeForm::cleanup()
61+
{
62+
}
63+
64+
void TestQgsAttributeForm::testFieldConstraint()
65+
{
66+
// make a temporary vector layer
67+
QString def = "Point?field=col0:integer";
68+
QgsVectorLayer* layer = new QgsVectorLayer( def, "test", "memory" );
69+
70+
// add a feature to the vector layer
71+
QgsFeature ft( layer->dataProvider()->fields(), 1 );
72+
ft.setAttribute( "col0", 0 );
73+
74+
// build a form for this feature
75+
QgsAttributeForm form( layer );
76+
form.setFeature( ft );
77+
78+
// testing stuff
79+
QSignalSpy spy( &form, SIGNAL( attributeChanged( QString, QVariant ) ) );
80+
QString validLabel = "col0<font color=\"green\">*</font>";
81+
QString invalidLabel = "col0<font color=\"red\">*</font>";
82+
83+
// set constraint
84+
layer->editFormConfig()->setConstraint( 0, "" );
85+
86+
// get wrapper
87+
QgsEditorWidgetWrapper *ww;
88+
ww = qobject_cast<QgsEditorWidgetWrapper*>( form.mWidgets[0] );
89+
90+
// no constraint so we expect a label with just the field name
91+
QLabel *label = form.mBuddyMap.value( ww->widget() );
92+
QCOMPARE( label->text(), QString( "col0" ) );
93+
94+
// set a not null constraint
95+
layer->editFormConfig()->setConstraint( 0, "col0 is not null" );
96+
97+
// set value to 1
98+
ww->setValue( 1 );
99+
QCOMPARE( spy.count(), 2 );
100+
QCOMPARE( label->text(), validLabel );
101+
102+
// set value to null
103+
spy.clear();
104+
ww->setValue( QVariant() );
105+
QCOMPARE( spy.count(), 2 );
106+
QCOMPARE( label->text(), invalidLabel );
107+
108+
// set value to 1
109+
spy.clear();
110+
ww->setValue( 1 );
111+
QCOMPARE( spy.count(), 2 );
112+
QCOMPARE( label->text(), validLabel );
113+
}
114+
115+
void TestQgsAttributeForm::testFieldMultiConstraints()
116+
{
117+
// make a temporary layer to check through
118+
QString def = "Point?field=col0:integer&field=col1:integer&field=col2:integer&field=col3:integer";
119+
QgsVectorLayer* layer = new QgsVectorLayer( def, "test", "memory" );
120+
121+
// add features to the vector layer
122+
QgsFeature ft( layer->dataProvider()->fields(), 1 );
123+
ft.setAttribute( "col0", 0 );
124+
ft.setAttribute( "col1", 1 );
125+
ft.setAttribute( "col2", 2 );
126+
ft.setAttribute( "col3", 3 );
127+
128+
// set constraints for each field
129+
layer->editFormConfig()->setConstraint( 0, "" );
130+
layer->editFormConfig()->setConstraint( 1, "" );
131+
layer->editFormConfig()->setConstraint( 2, "" );
132+
layer->editFormConfig()->setConstraint( 3, "" );
133+
134+
// build a form for this feature
135+
QgsAttributeForm form( layer );
136+
form.setFeature( ft );
137+
138+
// testing stuff
139+
QSignalSpy spy( &form, SIGNAL( attributeChanged( QString, QVariant ) ) );
140+
QString val = "<font color=\"green\">*</font>";
141+
QString inv = "<font color=\"red\">*</font>";
142+
143+
// get wrappers for each widget
144+
QgsEditorWidgetWrapper *ww0, *ww1, *ww2, *ww3;
145+
ww0 = qobject_cast<QgsEditorWidgetWrapper*>( form.mWidgets[0] );
146+
ww1 = qobject_cast<QgsEditorWidgetWrapper*>( form.mWidgets[1] );
147+
ww2 = qobject_cast<QgsEditorWidgetWrapper*>( form.mWidgets[2] );
148+
ww3 = qobject_cast<QgsEditorWidgetWrapper*>( form.mWidgets[3] );
149+
150+
// get label for wrappers
151+
QLabel *label0 = form.mBuddyMap.value( ww0->widget() );
152+
QLabel *label1 = form.mBuddyMap.value( ww1->widget() );
153+
QLabel *label2 = form.mBuddyMap.value( ww2->widget() );
154+
QLabel *label3 = form.mBuddyMap.value( ww3->widget() );
155+
156+
// no constraint so we expect a label with just the field name
157+
QCOMPARE( label0->text(), QString( "col0" ) );
158+
QCOMPARE( label1->text(), QString( "col1" ) );
159+
QCOMPARE( label2->text(), QString( "col2" ) );
160+
QCOMPARE( label3->text(), QString( "col3" ) );
161+
162+
// update constraint
163+
layer->editFormConfig()->setConstraint( 0, "col0 < (col1 * col2)" );
164+
layer->editFormConfig()->setConstraint( 1, "" );
165+
layer->editFormConfig()->setConstraint( 2, "" );
166+
layer->editFormConfig()->setConstraint( 3, "col0 = 2" );
167+
168+
// change value
169+
ww0->setValue( 2 ); // update col0
170+
QCOMPARE( spy.count(), 2 );
171+
172+
QCOMPARE( label0->text(), QString( "col0" + inv ) ); // 2 < ( 1 + 2 )
173+
QCOMPARE( label1->text(), QString( "col1" ) );
174+
QCOMPARE( label2->text(), QString( "col2" ) );
175+
QCOMPARE( label3->text(), QString( "col3" + val ) ); // 2 = 2
176+
177+
// change value
178+
spy.clear();
179+
ww0->setValue( 1 ); // update col0
180+
QCOMPARE( spy.count(), 2 );
181+
182+
QCOMPARE( label0->text(), QString( "col0" + val ) ); // 1 < ( 1 + 2 )
183+
QCOMPARE( label1->text(), QString( "col1" ) );
184+
QCOMPARE( label2->text(), QString( "col2" ) );
185+
QCOMPARE( label3->text(), QString( "col3" + inv ) ); // 2 = 1
186+
}
187+
188+
void TestQgsAttributeForm::testOKButtonStatus()
189+
{
190+
// make a temporary vector layer
191+
QString def = "Point?field=col0:integer";
192+
QgsVectorLayer* layer = new QgsVectorLayer( def, "test", "memory" );
193+
194+
// add a feature to the vector layer
195+
QgsFeature ft( layer->dataProvider()->fields(), 1 );
196+
ft.setAttribute( "col0", 0 );
197+
ft.setValid( true );
198+
199+
// build a form for this feature
200+
QgsAttributeForm form( layer );
201+
form.setFeature( ft );
202+
203+
QPushButton *okButton = form.mButtonBox->button( QDialogButtonBox::Ok );
204+
205+
// get wrapper
206+
QgsEditorWidgetWrapper *ww;
207+
ww = qobject_cast<QgsEditorWidgetWrapper*>( form.mWidgets[0] );
208+
209+
// testing stuff
210+
QSignalSpy spy1( &form, SIGNAL( attributeChanged( QString, QVariant ) ) );
211+
QSignalSpy spy2( layer, SIGNAL( editingStarted() ) );
212+
QSignalSpy spy3( layer, SIGNAL( editingStopped() ) );
213+
214+
// set constraint
215+
layer->editFormConfig()->setConstraint( 0, "" );
216+
217+
// no constraint but layer not editable : OK button disabled
218+
QCOMPARE( layer->isEditable(), false );
219+
QCOMPARE( okButton->isEnabled(), false );
220+
221+
// no constraint and editable layer : OK button enabled
222+
layer->startEditing();
223+
QCOMPARE( spy2.count(), 1 );
224+
QCOMPARE( layer->isEditable(), true );
225+
QCOMPARE( okButton->isEnabled(), true );
226+
227+
// invalid constraint and editable layer : OK button disabled
228+
layer->editFormConfig()->setConstraint( 0, "col0 = 0" );
229+
ww->setValue( 1 );
230+
QCOMPARE( okButton->isEnabled(), false );
231+
232+
// valid constraint and editable layer : OK button enabled
233+
layer->editFormConfig()->setConstraint( 0, "col0 = 2" );
234+
ww->setValue( 2 );
235+
QCOMPARE( okButton->isEnabled(), true );
236+
237+
// valid constraint and not editable layer : OK button disabled
238+
layer->rollBack();
239+
QCOMPARE( spy3.count(), 1 );
240+
QCOMPARE( layer->isEditable(), false );
241+
QCOMPARE( okButton->isEnabled(), false );
242+
}
243+
244+
QTEST_MAIN( TestQgsAttributeForm )
245+
#include "testqgsattributeform.moc"

0 commit comments

Comments
 (0)
Please sign in to comment.