Merge pull request #3079 from pblottiere/constraints

[FEATURE] widgets constraints
This commit is contained in:
Matthias Kuhn 2016-06-01 17:33:25 +02:00
commit 2b1560c5be
37 changed files with 1113 additions and 45 deletions

View File

@ -474,7 +474,48 @@ class QgsEditFormConfig : QObject
/**
* If set to false, the widget at the given index will be read-only.
*/
void setReadOnly(int idx, bool readOnly );
void setReadOnly( int idx, bool readOnly = true );
/**
* Returns the constraint expression of a specific field
* @param idx The index of the field
* @return the expression
* @note added in QGIS 2.16
*/
QString expression( int idx ) const;
/**
* Set the constraint expression for a specific field
* @param idx the field index
* @param str the constraint expression
* @note added in QGIS 2.16
*/
void setExpression( int idx, const QString& str );
/**
* Returns the constraint expression description of a specific filed.
* @param idx The index of the field
* @return the expression description
* @note added in QGIS 2.16
*/
QString expressionDescription( int idx ) const;
/**
* Set the constraint expression description for a specific field.
* @param idx The index of the field
* @param descr The description of the expression
* @note added in QGIS 2.16
*/
void setExpressionDescription( int idx, const QString &descr );
/**
* Returns if the field at fieldidx should be treated as NOT NULL value
*/
bool notNull( int fieldidx) const;
/**
* Set if the field at fieldidx should be treated as NOT NULL value
*/
void setNotNull( int idx, bool notnull = true );
/**
* If this returns true, the widget at the given index will receive its label on the previous line

View File

@ -165,7 +165,7 @@ class QgsField
/* Raise an exception if the arguments couldn't be parsed. */
sipNoMethod(sipParseErr, sipName_QgsField, sipName_convertCompatible, doc_QgsField_convertCompatible);
return NULL;
return nullptr;
%End
//! Allows direct construction of QVariants from fields.

View File

@ -88,6 +88,21 @@ class QgsEditorWidgetWrapper : QgsWidgetWrapper
*/
virtual void showIndeterminateState();
/**
* Update constraint.
* @param featureContext the feature to use to evaluate the constraint
* @note added in QGIS 2.16
*/
void updateConstraint( const QgsFeature &featureContext );
/**
* Get the current constraint status.
* @return true if the constraint is valid or if there's not constraint,
* false otherwise
* @note added in QGIS 2.16
*/
bool isValidConstraint() const;
signals:
/**
* Emit this signal, whenever the value changed.
@ -96,6 +111,16 @@ class QgsEditorWidgetWrapper : QgsWidgetWrapper
*/
void valueChanged( const QVariant& value );
/**
* Emit this signal when the constraint status changed.
* @brief constraintStatusChanged
* @param constraint represented as a string
* @param desc is the constraint description
* @param err the error represented as a string. Empty if none.
* @param status
*/
void constraintStatusChanged( const QString& constraint, const QString& err, bool status );
public slots:
/**
* Will be called when the feature changes
@ -162,4 +187,17 @@ class QgsEditorWidgetWrapper : QgsWidgetWrapper
* Will call the value() method to determine the emitted value
*/
void valueChanged();
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.
*/
virtual void updateConstraintWidgetStatus();
};

View File

@ -21,4 +21,18 @@ class QgsRelationReferenceWidgetWrapper : QgsEditorWidgetWrapper
public slots:
virtual void setValue( const QVariant& value );
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();
};

View File

@ -71,6 +71,8 @@ QgsAttributeTypeDialog::QgsAttributeTypeDialog( QgsVectorLayer *vl, int fieldIdx
QSettings settings;
restoreGeometry( settings.value( "/Windows/QgsAttributeTypeDialog/geometry" ).toByteArray() );
constraintExpression->setLayer( vl );
}
QgsAttributeTypeDialog::~QgsAttributeTypeDialog()
@ -163,16 +165,46 @@ void QgsAttributeTypeDialog::setWidgetV2Config( const QgsEditorWidgetConfig& con
mWidgetV2Config = config;
}
bool QgsAttributeTypeDialog::fieldEditable()
bool QgsAttributeTypeDialog::fieldEditable() const
{
return isFieldEditableCheckBox->isChecked();
}
bool QgsAttributeTypeDialog::labelOnTop()
void QgsAttributeTypeDialog::setNotNull( bool notNull )
{
notNullCheckBox->setChecked( notNull );
}
bool QgsAttributeTypeDialog::labelOnTop() const
{
return labelOnTopCheckBox->isChecked();
}
void QgsAttributeTypeDialog::setExpressionDescription( const QString &desc )
{
constraintExpressionDescription->setText( desc );
}
QString QgsAttributeTypeDialog::expressionDescription()
{
return constraintExpressionDescription->text();
}
bool QgsAttributeTypeDialog::notNull() const
{
return notNullCheckBox->isChecked();
}
void QgsAttributeTypeDialog::setExpression( const QString &str )
{
constraintExpression->setField( str );
}
QString QgsAttributeTypeDialog::expression() const
{
return constraintExpression->asExpression();
}
void QgsAttributeTypeDialog::setFieldEditable( bool editable )
{
isFieldEditableCheckBox->setChecked( editable );

View File

@ -69,6 +69,11 @@ class APP_EXPORT QgsAttributeTypeDialog: public QDialog, private Ui::QgsAttribut
*/
void setLabelOnTop( bool onTop );
/**
* Getter for checkbox for label on top of field
*/
bool labelOnTop() const;
/**
* Setter for checkbox for editable state of field
*/
@ -77,12 +82,43 @@ class APP_EXPORT QgsAttributeTypeDialog: public QDialog, private Ui::QgsAttribut
/**
* Getter for checkbox for editable state of field
*/
bool fieldEditable();
bool fieldEditable() const;
/**
* Getter for checkbox for label on top of field
* Setter for checkbox for not null
*/
bool labelOnTop();
void setNotNull( bool notNull );
/**
* Getter for checkbox for not null
*/
bool notNull() const;
/*
* Setter for constraint expression description
* @param desc the expression description
* @note added in QGIS 2.16
**/
void setExpressionDescription( const QString &desc );
/*
* Getter for constraint expression description
* @return the expression description
* @note added in QGIS 2.16
**/
QString expressionDescription();
/**
* Getter for the constraint expression
* @note added in QGIS 2.16
*/
QString expression() const;
/**
* Setter for the constraint expression
* @note added in QGIS 2.16
*/
void setExpression( const QString &str );
private slots:
/**

View File

@ -527,6 +527,9 @@ void QgsFieldsProperties::attributeTypeDialog()
attributeTypeDialog.setFieldEditable( cfg.mEditable );
attributeTypeDialog.setLabelOnTop( cfg.mLabelOnTop );
attributeTypeDialog.setNotNull( cfg.mNotNull );
attributeTypeDialog.setExpression( cfg.mConstraint );
attributeTypeDialog.setExpressionDescription( cfg.mConstraintDescription );
attributeTypeDialog.setWidgetV2Config( cfg.mEditorWidgetV2Config );
attributeTypeDialog.setWidgetV2Type( cfg.mEditorWidgetV2Type );
@ -536,6 +539,9 @@ void QgsFieldsProperties::attributeTypeDialog()
cfg.mEditable = attributeTypeDialog.fieldEditable();
cfg.mLabelOnTop = attributeTypeDialog.labelOnTop();
cfg.mNotNull = attributeTypeDialog.notNull();
cfg.mConstraintDescription = attributeTypeDialog.expressionDescription();
cfg.mConstraint = attributeTypeDialog.expression();
cfg.mEditorWidgetV2Type = attributeTypeDialog.editorWidgetV2Type();
cfg.mEditorWidgetV2Config = attributeTypeDialog.editorWidgetV2Config();
@ -908,6 +914,9 @@ void QgsFieldsProperties::apply()
mLayer->editFormConfig()->setReadOnly( i, !cfg.mEditable );
mLayer->editFormConfig()->setLabelOnTop( i, cfg.mLabelOnTop );
mLayer->editFormConfig()->setNotNull( i, cfg.mNotNull );
mLayer->editFormConfig()->setExpressionDescription( i, cfg.mConstraintDescription );
mLayer->editFormConfig()->setExpression( i, cfg.mConstraint );
mLayer->editFormConfig()->setWidgetType( idx, cfg.mEditorWidgetV2Type );
mLayer->editFormConfig()->setWidgetConfig( idx, cfg.mEditorWidgetV2Config );
@ -974,6 +983,8 @@ QgsFieldsProperties::FieldConfig::FieldConfig()
: mEditable( true )
, mEditableEnabled( true )
, mLabelOnTop( false )
, mNotNull( false )
, mConstraintDescription( QString() )
, mButton( nullptr )
{
}
@ -985,6 +996,9 @@ QgsFieldsProperties::FieldConfig::FieldConfig( QgsVectorLayer* layer, int idx )
mEditableEnabled = layer->fields().fieldOrigin( idx ) != QgsFields::OriginJoin
&& layer->fields().fieldOrigin( idx ) != QgsFields::OriginExpression;
mLabelOnTop = layer->editFormConfig()->labelOnTop( idx );
mNotNull = layer->editFormConfig()->notNull( idx );
mConstraint = layer->editFormConfig()->expression( idx );
mConstraintDescription = layer->editFormConfig()->expressionDescription( idx );
mEditorWidgetV2Type = layer->editFormConfig()->widgetType( idx );
mEditorWidgetV2Config = layer->editFormConfig()->widgetConfig( idx );

View File

@ -92,6 +92,9 @@ class APP_EXPORT QgsFieldsProperties : public QWidget, private Ui_QgsFieldsPrope
bool mEditable;
bool mEditableEnabled;
bool mLabelOnTop;
bool mNotNull;
QString mConstraint;
QString mConstraintDescription;
QPushButton* mButton;
QString mEditorWidgetV2Type;
QMap<QString, QVariant> mEditorWidgetV2Config;

View File

@ -119,6 +119,46 @@ bool QgsEditFormConfig::labelOnTop( int idx ) const
return false;
}
QString QgsEditFormConfig::expression( int idx ) const
{
QString expr;
if ( idx >= 0 && idx < mFields.count() )
expr = mConstraints.value( mFields.at( idx ).name(), QString() );
return expr;
}
void QgsEditFormConfig::setExpression( int idx, const QString& str )
{
if ( idx >= 0 && idx < mFields.count() )
mConstraints[ mFields.at( idx ).name()] = str;
}
QString QgsEditFormConfig::expressionDescription( int idx ) const
{
QString description;
if ( idx >= 0 && idx < mFields.count() )
description = mConstraintsDescription[ mFields.at( idx ).name()];
return description;
}
void QgsEditFormConfig::setExpressionDescription( int idx, const QString &descr )
{
if ( idx >= 0 && idx < mFields.count() )
mConstraintsDescription[ mFields.at( idx ).name()] = descr;
}
bool QgsEditFormConfig::notNull( int idx ) const
{
if ( idx >= 0 && idx < mFields.count() )
return mNotNull.value( mFields.at( idx ).name(), false );
else
return false;
}
void QgsEditFormConfig::setReadOnly( int idx, bool readOnly )
{
if ( idx >= 0 && idx < mFields.count() )
@ -131,6 +171,12 @@ void QgsEditFormConfig::setLabelOnTop( int idx, bool onTop )
mLabelOnTop[ mFields.at( idx ).name()] = onTop;
}
void QgsEditFormConfig::setNotNull( int idx, bool notnull )
{
if ( idx >= 0 && idx < mFields.count() )
mNotNull[ mFields.at( idx ).name()] = notnull;
}
void QgsEditFormConfig::readXml( const QDomNode& node )
{
QDomNode editFormNode = node.namedItem( "editform" );
@ -280,7 +326,6 @@ void QgsEditFormConfig::writeXml( QDomNode& node ) const
efifpField.appendChild( doc.createTextNode( QgsProject::instance()->writePath( initFilePath() ) ) );
node.appendChild( efifpField );
QDomElement eficField = doc.createElement( "editforminitcode" );
eficField.appendChild( doc.createCDATASection( initCode() ) );
node.appendChild( eficField );
@ -337,6 +382,7 @@ void QgsEditFormConfig::writeXml( QDomNode& node ) const
{
QDomElement widgetElem = doc.createElement( "widget" );
widgetElem.setAttribute( "name", configIt.key() );
// widgetElem.setAttribute( "notNull", );
QDomElement configElem = doc.createElement( "config" );
widgetElem.appendChild( configElem );

View File

@ -484,12 +484,12 @@ class CORE_EXPORT QgsEditFormConfig : public QObject
QgsEditorWidgetConfig widgetConfig( const QString& widgetName ) const;
/**
* Remove the configuration for the editor widget used to represent the field at the given index
*
* @param fieldIdx The index of the field
*
* @return true if successful, false if the field does not exist
*/
* Remove the configuration for the editor widget used to represent the field at the given index
*
* @param fieldIdx The index of the field
*
* @return true if successful, false if the field does not exist
*/
bool removeWidgetConfig( int fieldIdx );
/**
@ -512,6 +512,47 @@ class CORE_EXPORT QgsEditFormConfig : public QObject
*/
void setReadOnly( int idx, bool readOnly = true );
/**
* Returns the constraint expression of a specific field
* @param idx The index of the field
* @return the expression
* @note added in QGIS 2.16
*/
QString expression( int idx ) const;
/**
* Set the constraint expression for a specific field
* @param idx the field index
* @param str the constraint expression
* @note added in QGIS 2.16
*/
void setExpression( int idx, const QString& str );
/**
* Returns the constraint expression description of a specific filed.
* @param idx The index of the field
* @return the expression description
* @note added in QGIS 2.16
*/
QString expressionDescription( int idx ) const;
/**
* Set the constraint expression description for a specific field.
* @param idx The index of the field
* @param descr The description of the expression
* @note added in QGIS 2.16
*/
void setExpressionDescription( int idx, const QString &descr );
/**
* Returns if the field at fieldidx should be treated as NOT NULL value
*/
bool notNull( int fieldidx ) const;
/**
* Set if the field at fieldidx should be treated as NOT NULL value
*/
void setNotNull( int idx, bool notnull = true );
/**
* If this returns true, the widget at the given index will receive its label on the previous line
* while if it returns false, the widget will receive its label on the left hand side.
@ -631,8 +672,11 @@ class CORE_EXPORT QgsEditFormConfig : public QObject
/** Map that stores the tab for attributes in the edit form. Key is the tab order and value the tab name*/
QList< TabData > mTabs;
QMap< QString, QString> mConstraints;
QMap< QString, QString> mConstraintsDescription;
QMap< QString, bool> mFieldEditables;
QMap< QString, bool> mLabelOnTop;
QMap< QString, bool> mNotNull;
QMap<QString, QString> mEditorWidgetV2Types;
QMap<QString, QgsEditorWidgetConfig > mWidgetConfigs;

View File

@ -16,6 +16,8 @@
#include <QString>
#include <QVariant>
#ifndef QGSEDITORWIDGETCONFIG_H
#define QGSEDITORWIDGETCONFIG_H
/**
* Holds a set of configuration parameters for a editor widget wrapper.
@ -30,4 +32,6 @@
* You get these passed, for every new widget wrapper.
*/
typedef QMap<QString, QVariant> QgsEditorWidgetConfig;
typedef QVariantMap QgsEditorWidgetConfig;
#endif // QGSEDITORWIDGETCONFIG_H

View File

@ -288,6 +288,12 @@ int QgsFeature::fieldNameIndex( const QString& fieldName ) const
return d->fields.fieldNameIndex( fieldName );
}
/***************************************************************************
* This class is considered CRITICAL and any change MUST be accompanied with
* full unit tests in testqgsfeature.cpp.
* See details in QEP #17
****************************************************************************/
QDataStream& operator<<( QDataStream& out, const QgsFeature& feature )
{
out << feature.id();

View File

@ -1105,13 +1105,13 @@ QgsFeatureIterator QgsVectorLayer::getFeatures( const QgsFeatureRequest& request
}
bool QgsVectorLayer::addFeature( QgsFeature& f, bool alsoUpdateExtent )
bool QgsVectorLayer::addFeature( QgsFeature& feature, bool alsoUpdateExtent )
{
Q_UNUSED( alsoUpdateExtent ); // TODO[MD]
if ( !mValid || !mEditBuffer || !mDataProvider )
return false;
bool success = mEditBuffer->addFeature( f );
bool success = mEditBuffer->addFeature( feature );
if ( success )
updateExtents();

View File

@ -975,11 +975,11 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer
QgsFeatureIterator getFeatures( const QgsFeatureRequest& request = QgsFeatureRequest() );
/** Adds a feature
@param f feature to add
@param feature feature to add
@param alsoUpdateExtent If True, will also go to the effort of e.g. updating the extents.
@return True in case of success and False in case of error
*/
bool addFeature( QgsFeature& f, bool alsoUpdateExtent = true );
bool addFeature( QgsFeature& feature, bool alsoUpdateExtent = true );
/** Updates an existing feature. This method needs to query the datasource
on every call. Consider using {@link changeAttributeValue()} or

View File

@ -251,6 +251,10 @@ void QgsEditorWidgetRegistry::readMapLayer( QgsMapLayer* mapLayer, const QDomEle
vectorLayer->editFormConfig()->setReadOnly( idx, ewv2CfgElem.attribute( "fieldEditable", "1" ) != "1" );
vectorLayer->editFormConfig()->setLabelOnTop( idx, ewv2CfgElem.attribute( "labelOnTop", "0" ) == "1" );
vectorLayer->editFormConfig()->setNotNull( idx, ewv2CfgElem.attribute( "notNull", "0" ) == "1" );
vectorLayer->editFormConfig()->setExpression( idx, ewv2CfgElem.attribute( "constraint", QString() ) );
vectorLayer->editFormConfig()->setExpressionDescription( idx, ewv2CfgElem.attribute( "constraintDescription", QString() ) );
vectorLayer->editFormConfig()->setWidgetConfig( idx, cfg );
}
else
@ -307,6 +311,9 @@ void QgsEditorWidgetRegistry::writeMapLayer( QgsMapLayer* mapLayer, QDomElement&
QDomElement ewv2CfgElem = doc.createElement( "widgetv2config" );
ewv2CfgElem.setAttribute( "fieldEditable", !vectorLayer->editFormConfig()->readOnly( idx ) );
ewv2CfgElem.setAttribute( "labelOnTop", vectorLayer->editFormConfig()->labelOnTop( idx ) );
ewv2CfgElem.setAttribute( "notNull", vectorLayer->editFormConfig()->notNull( idx ) );
ewv2CfgElem.setAttribute( "constraint", vectorLayer->editFormConfig()->expression( idx ) );
ewv2CfgElem.setAttribute( "constraintDescription", vectorLayer->editFormConfig()->expressionDescription( idx ) );
mWidgetFactories[widgetType]->writeConfig( vectorLayer->editFormConfig()->widgetConfig( idx ), ewv2CfgElem, doc, vectorLayer, idx );

View File

@ -22,6 +22,7 @@
QgsEditorWidgetWrapper::QgsEditorWidgetWrapper( QgsVectorLayer* vl, int fieldIdx, QWidget* editor, QWidget* parent )
: QgsWidgetWrapper( vl, editor, parent )
, mValidConstraint( true )
, mFieldIdx( fieldIdx )
{
}
@ -60,6 +61,7 @@ void QgsEditorWidgetWrapper::setEnabled( bool enabled )
void QgsEditorWidgetWrapper::setFeature( const QgsFeature& feature )
{
mFeature = feature;
setValue( feature.attribute( mFieldIdx ) );
}
@ -92,3 +94,78 @@ void QgsEditorWidgetWrapper::valueChanged()
{
emit valueChanged( value() );
}
void QgsEditorWidgetWrapper::updateConstraintWidgetStatus()
{
if ( mValidConstraint )
widget()->setStyleSheet( QString() );
else
widget()->setStyleSheet( "background-color: #dd7777;" );
}
void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft )
{
bool toEmit( false );
QString errStr( tr( "predicate is True" ) );
QString expression = layer()->editFormConfig()->expression( mFieldIdx );
QString description;
QVariant value = ft.attribute( mFieldIdx );
if ( ! expression.isEmpty() )
{
description = layer()->editFormConfig()->expressionDescription( mFieldIdx );
QgsExpressionContext context =
QgsExpressionContextUtils::createFeatureBasedContext( ft, *ft.fields() );
context << QgsExpressionContextUtils::layerScope( layer() );
context.setFeature( ft );
QgsExpression expr( expression );
mValidConstraint = expr.evaluate( &context ).toBool();
if ( expr.hasParserError() )
errStr = expr.parserErrorString();
else if ( expr.hasEvalError() )
errStr = expr.evalErrorString();
else if ( ! mValidConstraint )
errStr = tr( "predicate is False" );
toEmit = true;
}
else
mValidConstraint = true;
if ( layer()->editFormConfig()->notNull( mFieldIdx ) )
{
if ( !expression.isEmpty() )
{
QString fieldName = ft.fields()->field( mFieldIdx ).name();
expression = "( " + expression + " ) AND ( " + fieldName + " IS NOT NULL)";
description = "( " + description + " ) AND NotNull";
}
else
{
description = "NotNull";
expression = "NotNull";
}
mValidConstraint = mValidConstraint && !value.isNull();
if ( value.isNull() )
errStr = tr( "predicate is False" );
toEmit = true;
}
if ( toEmit )
{
updateConstraintWidgetStatus();
emit constraintStatusChanged( expression, description, errStr, mValidConstraint );
}
}
bool QgsEditorWidgetWrapper::isValidConstraint() const
{
return mValidConstraint;
}

View File

@ -110,6 +110,21 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
*/
virtual void showIndeterminateState() {}
/**
* Update constraint.
* @param featureContext the feature to use to evaluate the constraint
* @note added in QGIS 2.16
*/
void updateConstraint( const QgsFeature &featureContext );
/**
* Get the current constraint status.
* @return true if the constraint is valid or if there's not constraint,
* false otherwise
* @note added in QGIS 2.16
*/
bool isValidConstraint() const;
signals:
/**
* Emit this signal, whenever the value changed.
@ -118,6 +133,16 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
*/
void valueChanged( const QVariant& value );
/**
* Emit this signal when the constraint status changed.
* @brief constraintStatusChanged
* @param constraint represented as a string
* @param desc is the constraint description
* @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 );
public slots:
/**
* Will be called when the feature changes
@ -185,8 +210,28 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
*/
void valueChanged();
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
*/
virtual void updateConstraintWidgetStatus();
/**
* Boolean storing the current validity of the constraint for this widget.
*/
bool mValidConstraint;
private:
int mFieldIdx;
QgsFeature mFeature;
};
// We'll use this class inside a QVariant in the widgets properties

View File

@ -77,3 +77,8 @@ void QgsColorWidgetWrapper::setValue( const QVariant& value )
if ( mColorButton )
mColorButton->setColor( !value.isNull() ? QColor( value.toString() ) : QColor() );
}
void QgsColorWidgetWrapper::updateConstraintWidgetStatus()
{
// nothing
}

View File

@ -46,6 +46,8 @@ class GUI_EXPORT QgsColorWidgetWrapper : public QgsEditorWidgetWrapper
void setValue( const QVariant& value ) override;
private:
void updateConstraintWidgetStatus() override;
QgsColorButtonV2* mColorButton;
};

View File

@ -95,8 +95,9 @@ void QgsExternalResourceWidgetWrapper::initWidget( QWidget* editor )
{
fle->setNullValue( QSettings().value( "qgis/nullValue", "NULL" ).toString() );
}
connect( mLineEdit, SIGNAL( textChanged( QString ) ), this, SLOT( valueChanged( QString ) ) );
}
else
mLineEdit = editor->findChild<QLineEdit*>();
if ( mQgsWidget )
{
@ -138,6 +139,10 @@ void QgsExternalResourceWidgetWrapper::initWidget( QWidget* editor )
mQgsWidget->fileWidget()->setFilter( config( "FileWidgetFilter" ).toString() );
}
}
if ( mLineEdit )
connect( mLineEdit, SIGNAL( textChanged( QString ) ), this, SLOT( valueChanged( QString ) ) );
}
void QgsExternalResourceWidgetWrapper::setValue( const QVariant& value )
@ -182,3 +187,14 @@ void QgsExternalResourceWidgetWrapper::setEnabled( bool enabled )
if ( mQgsWidget )
mQgsWidget->setReadOnly( !enabled );
}
void QgsExternalResourceWidgetWrapper::updateConstraintWidgetStatus()
{
if ( mLineEdit )
{
if ( mValidConstraint )
mLineEdit->setStyleSheet( QString() );
else
mLineEdit->setStyleSheet( "QgsFilterLineEdit { background-color: #dd7777; }" );
}
}

View File

@ -54,6 +54,8 @@ class GUI_EXPORT QgsExternalResourceWidgetWrapper : public QgsEditorWidgetWrappe
void setEnabled( bool enabled ) override;
private:
void updateConstraintWidgetStatus() override;
QLineEdit* mLineEdit;
QLabel* mLabel;
QgsExternalResourceWidget* mQgsWidget;

View File

@ -151,3 +151,16 @@ void QgsFileNameWidgetWrapper::selectFileName()
if ( mLabel )
mLineEdit->setText( fileName );
}
void QgsFileNameWidgetWrapper::updateConstraintWidgetStatus()
{
if ( mLineEdit )
{
if ( mValidConstraint )
mLineEdit->setStyleSheet( QString() );
else
{
mLineEdit->setStyleSheet( "QgsFilterLineEdit { background-color: #dd7777; }" );
}
}
}

View File

@ -51,6 +51,8 @@ class GUI_EXPORT QgsFileNameWidgetWrapper : public QgsEditorWidgetWrapper
void setValue( const QVariant& value ) override;
private:
void updateConstraintWidgetStatus() override;
QLineEdit* mLineEdit;
QPushButton* mPushButton;
QLabel* mLabel;

View File

@ -266,3 +266,16 @@ void QgsPhotoWidgetWrapper::setEnabled( bool enabled )
if ( mButton )
mButton->setEnabled( enabled );
}
void QgsPhotoWidgetWrapper::updateConstraintWidgetStatus()
{
if ( mLineEdit )
{
if ( mValidConstraint )
mLineEdit->setStyleSheet( QString() );
else
{
mLineEdit->setStyleSheet( "QgsFilterLineEdit { background-color: #dd7777; }" );
}
}
}

View File

@ -65,6 +65,8 @@ class GUI_EXPORT QgsPhotoWidgetWrapper : public QgsEditorWidgetWrapper
void loadPixmap( const QString& fileName );
private:
void updateConstraintWidgetStatus() override;
//! This label is used as a container to display the picture
QLabel* mPhotoLabel;
//! This label is used as a container to display a picture that scales with the dialog layout.

View File

@ -38,7 +38,7 @@ QgsEditorWidgetConfig QgsRangeWidgetFactory::readConfig( const QDomElement& conf
{
Q_UNUSED( layer );
Q_UNUSED( fieldIdx );
QMap<QString, QVariant> cfg;
QgsEditorWidgetConfig cfg;
cfg.insert( "Style", configElement.attribute( "Style" ) );
cfg.insert( "Min", configElement.attribute( "Min" ) );

View File

@ -55,7 +55,7 @@ QgsRelationReferenceConfigDlg::QgsRelationReferenceConfigDlg( QgsVectorLayer* vl
}
}
void QgsRelationReferenceConfigDlg::setConfig( const QMap<QString, QVariant>& config )
void QgsRelationReferenceConfigDlg::setConfig( const QgsEditorWidgetConfig& config )
{
if ( config.contains( "AllowNULL" ) )
{

View File

@ -46,7 +46,7 @@ QgsEditorWidgetConfig QgsRelationReferenceFactory::readConfig( const QDomElement
{
Q_UNUSED( layer );
Q_UNUSED( fieldIdx );
QMap<QString, QVariant> cfg;
QgsEditorWidgetConfig cfg;
cfg.insert( "AllowNULL", configElement.attribute( "AllowNULL" ) == "1" );
cfg.insert( "OrderByValue", configElement.attribute( "OrderByValue" ) == "1" );

View File

@ -139,3 +139,14 @@ void QgsRelationReferenceWidgetWrapper::foreignKeyChanged( QVariant value )
}
emit valueChanged( value );
}
void QgsRelationReferenceWidgetWrapper::updateConstraintWidgetStatus()
{
if ( mWidget )
{
if ( mValidConstraint )
mWidget->setStyleSheet( QString() );
else
mWidget->setStyleSheet( ".QComboBox { background-color: #dd7777; }" );
}
}

View File

@ -59,6 +59,20 @@ class GUI_EXPORT QgsRelationReferenceWidgetWrapper : public QgsEditorWidgetWrapp
private slots:
void foreignKeyChanged( QVariant value );
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() override;
private:
QgsRelationReferenceWidget* mWidget;
QgsMapCanvas* mCanvas;

View File

@ -188,3 +188,16 @@ void QgsWebViewWidgetWrapper::selectFileName()
if ( mLineEdit )
mLineEdit->setText( filePath );
}
void QgsWebViewWidgetWrapper::updateConstraintWidgetStatus()
{
if ( mLineEdit )
{
if ( mValidConstraint )
mLineEdit->setStyleSheet( QString() );
else
{
mLineEdit->setStyleSheet( "QgsFilterLineEdit { background-color: #dd7777; }" );
}
}
}

View File

@ -53,6 +53,8 @@ class GUI_EXPORT QgsWebViewWidgetWrapper : public QgsEditorWidgetWrapper
void selectFileName();
private:
void updateConstraintWidgetStatus() override;
//! This label is used as a container to display the picture
QWebView* mWebView;
//! The line edit containing the path to the picture

View File

@ -53,6 +53,7 @@ QgsAttributeForm::QgsAttributeForm( QgsVectorLayer* vl, const QgsFeature &featur
, mMessageBar( nullptr )
, mMultiEditUnsavedMessageBarItem( nullptr )
, mMultiEditMessageBarItem( nullptr )
, mInvalidConstraintMessage( nullptr )
, mContext( context )
, mButtonBox( nullptr )
, mSearchButtonBox( nullptr )
@ -79,6 +80,9 @@ QgsAttributeForm::QgsAttributeForm( QgsVectorLayer* vl, const QgsFeature &featur
connect( vl, SIGNAL( beforeAddingExpressionField( QString ) ), this, SLOT( preventFeatureRefresh() ) );
connect( vl, SIGNAL( beforeRemovingExpressionField( int ) ), this, SLOT( preventFeatureRefresh() ) );
connect( vl, SIGNAL( selectionChanged() ), this, SLOT( layerSelectionChanged() ) );
// constraints management
updateAllConstaints();
}
QgsAttributeForm::~QgsAttributeForm()
@ -655,6 +659,157 @@ void QgsAttributeForm::onAttributeChanged( const QVariant& value )
//nothing to do
break;
}
if ( eww->layer()->editFormConfig()->notNull( eww->fieldIdx() ) )
{
QLabel* buddy = mBuddyMap.value( eww->widget() );
if ( buddy )
{
if ( !buddy->property( "originalText" ).isValid() )
buddy->setProperty( "originalText", buddy->text() );
QString text = buddy->property( "originalText" ).toString();
if ( value.isNull() )
{
// not good
#if QT_VERSION >= 0x050000
buddy->setText( QString( "%1<font color=\"red\">❌</font>" ).arg( text ) );
#else
buddy->setText( QString( "%1<font color=\"red\">*</font>" ).arg( text ) );
#endif
}
else
{
// good
#if QT_VERSION >= 0x050000
buddy->setText( QString( "%1<font color=\"green\">✔</font>" ).arg( text ) );
#else
buddy->setText( QString( "%1<font color=\"green\">*</font>" ).arg( text ) );
#endif
}
}
}
updateConstraints( eww );
// emit
emit attributeChanged( eww->field().name(), value );
}
void QgsAttributeForm::updateAllConstaints()
{
Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
{
QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
if ( eww )
updateConstraints( eww );
}
}
void QgsAttributeForm::updateConstraints( QgsEditorWidgetWrapper *eww )
{
// get the current feature set in the form
QgsFeature ft;
if ( currentFormFeature( ft ) )
{
// update eww constraint
eww->updateConstraint( ft );
// update eww dependencies constraint
QList<QgsEditorWidgetWrapper*> deps;
constraintDependencies( eww, deps );
Q_FOREACH ( QgsEditorWidgetWrapper* depsEww, deps )
depsEww->updateConstraint( ft );
// sync ok button status
synchronizeEnabledState();
}
}
bool QgsAttributeForm::currentFormFeature( QgsFeature &feature )
{
bool rc = true;
feature = QgsFeature( mFeature );
QgsAttributes src = feature.attributes();
QgsAttributes dst = feature.attributes();
Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
{
QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
if ( eww && dst.count() > eww->fieldIdx() )
{
QVariant dstVar = dst.at( eww->fieldIdx() );
QVariant srcVar = eww->value();
// need to check dstVar.isNull() != srcVar.isNull()
// otherwise if dstVar=NULL and scrVar=0, then dstVar = srcVar
if (( dstVar != srcVar || dstVar.isNull() != srcVar.isNull() ) && srcVar.isValid() && !mLayer->editFormConfig()->readOnly( eww->fieldIdx() ) )
dst[eww->fieldIdx()] = srcVar;
}
else
{
rc = false;
break;
}
}
feature.setAttributes( dst );
return rc;
}
void QgsAttributeForm::clearInvalidConstraintsMessage()
{
mInvalidConstraintMessage->clear();
mInvalidConstraintMessage->setStyleSheet( "" );
}
void QgsAttributeForm::displayInvalidConstraintMessage( const QStringList &f,
const QStringList &d )
{
clearInvalidConstraintsMessage();
// show only the third first errors (to avoid a too long label)
int max = 3;
int size = f.size() > max ? max : f.size();
QString descriptions;
for ( int i = 0; i < size; i++ )
descriptions += QString( "<li>%1: <i>%2</i></li>" ).arg( f[i] ).arg( d[i] );
QString icPath = QgsApplication::iconPath( "/mIconWarn.png" );
QString title = QString( "<img src=\"%1\"> <b>%2:" ).arg( icPath ).arg( tr( "Invalid fields" ) );
QString msg = QString( "%1</b><ul>%2</ul>" ).arg( title ).arg( descriptions ) ;
mInvalidConstraintMessage->setText( msg );
mInvalidConstraintMessage->setStyleSheet( "QLabel { background-color : #ffc800; }" );
}
bool QgsAttributeForm::currentFormValidConstraints( QStringList &invalidFields,
QStringList &descriptions )
{
bool valid( true );
Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
{
QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
if ( eww )
{
if ( ! eww->isValidConstraint() )
{
invalidFields.append( eww->field().name() );
QString desc = eww->layer()->editFormConfig()->expressionDescription( eww->fieldIdx() );
descriptions.append( desc );
valid = false; // continue to get all invalif fields
}
}
}
return valid;
}
void QgsAttributeForm::onAttributeAdded( int idx )
@ -714,6 +869,70 @@ void QgsAttributeForm::onUpdatedFields()
setFeature( mFeature );
}
void QgsAttributeForm::onConstraintStatusChanged( const QString& constraint,
const QString &description, const QString& err, bool ok )
{
QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( sender() );
Q_ASSERT( eww );
QLabel* buddy = mBuddyMap.value( eww->widget() );
if ( buddy )
{
QString tooltip = tr( "Description: " ) + description + "\n" +
tr( "Raw expression: " ) + constraint + "\n" + tr( "Constraint: " ) + err;
buddy->setToolTip( tooltip );
if ( !buddy->property( "originalText" ).isValid() )
buddy->setProperty( "originalText", buddy->text() );
QString text = buddy->property( "originalText" ).toString();
if ( !ok )
{
// not good
buddy->setText( QString( "%1<font color=\"red\">*</font>" ).arg( text ) );
}
else
{
// good
buddy->setText( QString( "%1<font color=\"green\">*</font>" ).arg( text ) );
}
}
}
void QgsAttributeForm::constraintDependencies( QgsEditorWidgetWrapper *w,
QList<QgsEditorWidgetWrapper*> &wDeps )
{
QString name = w->field().name();
// for each widget in the current form
Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets )
{
// get the wrapper
QgsEditorWidgetWrapper* eww = qobject_cast<QgsEditorWidgetWrapper*>( ww );
if ( eww )
{
// compare name to not compare w to itself
QString ewwName = eww->field().name();
if ( name != ewwName )
{
// get expression and referencedColumns
QgsExpression expr = eww->layer()->editFormConfig()->expression( eww->fieldIdx() );
Q_FOREACH ( const QString& colName, expr.referencedColumns() )
{
if ( name == colName )
{
wDeps.append( eww );
break;
}
}
}
}
}
}
void QgsAttributeForm::preventFeatureRefresh()
{
mPreventFeatureRefresh = true;
@ -752,6 +971,18 @@ void QgsAttributeForm::synchronizeEnabledState()
ww->setEnabled( isEditable && fieldEditable );
}
// push a message and disable the OK button if constraints are invalid
clearInvalidConstraintsMessage();
QStringList invalidFields, descriptions;
bool validConstraint = currentFormValidConstraints( invalidFields, descriptions );
if ( ! validConstraint )
displayInvalidConstraintMessage( invalidFields, descriptions );
isEditable = isEditable & validConstraint;
// change ok button status
QPushButton* okButton = mButtonBox->button( QDialogButtonBox::Ok );
if ( okButton )
okButton->setEnabled( isEditable );
@ -794,6 +1025,10 @@ void QgsAttributeForm::init()
mMessageBar = new QgsMessageBar( this );
mMessageBar->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
vl->addWidget( mMessageBar );
mInvalidConstraintMessage = new QLabel( this );
vl->addWidget( mInvalidConstraintMessage );
setLayout( vl );
// Get a layout
@ -899,7 +1134,7 @@ void QgsAttributeForm::init()
bool labelOnTop = mLayer->editFormConfig()->labelOnTop( idx );
// This will also create the widget
QWidget *l = new QLabel( fieldName );
QLabel *l = new QLabel( fieldName );
QgsEditorWidgetWrapper* eww = QgsEditorWidgetRegistry::instance()->create( widgetType, mLayer, idx, widgetConfig, nullptr, this, mContext );
QWidget* w = nullptr;
@ -909,12 +1144,18 @@ void QgsAttributeForm::init()
w = formWidget;
mFormEditorWidgets.insert( idx, formWidget );
formWidget->createSearchWidgetWrappers( widgetType, idx, widgetConfig, mContext );
l->setBuddy( eww->widget() );
}
else
{
w = new QLabel( QString( "<p style=\"color: red; font-style: italic;\">Failed to create widget with type '%1'</p>" ).arg( widgetType ) );
}
if ( w )
w->setObjectName( field.name() );
if ( eww )
addWidgetWrapper( eww );
@ -1011,7 +1252,7 @@ void QgsAttributeForm::init()
}
mSearchButtonBox->setVisible( mMode == SearchMode );
connectWrappers();
afterWidgetInit();
connect( mButtonBox, SIGNAL( accepted() ), this, SLOT( accept() ) );
connect( mButtonBox, SIGNAL( rejected() ), this, SLOT( resetValues() ) );
@ -1251,6 +1492,8 @@ QgsAttributeForm::WidgetInfo QgsAttributeForm::createWidgetFromDef( const QgsAtt
mypLabel->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
}
mypLabel->setBuddy( widgetInfo.widget );
if ( widgetInfo.labelOnTop )
{
QVBoxLayout* c = new QVBoxLayout();
@ -1350,7 +1593,7 @@ void QgsAttributeForm::createWrappers()
}
}
void QgsAttributeForm::connectWrappers()
void QgsAttributeForm::afterWidgetInit()
{
bool isFirstEww = true;
@ -1367,8 +1610,20 @@ void QgsAttributeForm::connectWrappers()
}
connect( eww, SIGNAL( valueChanged( const QVariant& ) ), this, SLOT( onAttributeChanged( const QVariant& ) ) );
connect( eww, SIGNAL( constraintStatusChanged( QString, QString, QString, bool ) ),
this, SLOT( onConstraintStatusChanged( QString, QString, QString, bool ) ) );
}
}
// Update buddy widget list
mBuddyMap.clear();
QList<QLabel*> labels = findChildren<QLabel*>();
Q_FOREACH ( QLabel* label, labels )
{
if ( label->buddy() )
mBuddyMap.insert( label->buddy(), label );
}
}

View File

@ -21,6 +21,7 @@
#include "qgsattributeeditorcontext.h"
#include <QWidget>
#include <QLabel>
#include <QDialogButtonBox>
class QgsAttributeFormInterface;
@ -233,7 +234,8 @@ class GUI_EXPORT QgsAttributeForm : public QWidget
void onAttributeAdded( int idx );
void onAttributeDeleted( int idx );
void onUpdatedFields();
void onConstraintStatusChanged( const QString& constraint,
const QString &description, const QString& err, bool ok );
void preventFeatureRefresh();
void synchronizeEnabledState();
void layerSelectionChanged();
@ -282,7 +284,7 @@ class GUI_EXPORT QgsAttributeForm : public QWidget
* Called once maximally.
*/
void createWrappers();
void connectWrappers();
void afterWidgetInit();
void scanForEqualAttributes( QgsFeatureIterator& fit, QSet< int >& mixedValueFields, QHash< int, QVariant >& fieldSharedValues ) const;
@ -296,11 +298,22 @@ class GUI_EXPORT QgsAttributeForm : public QWidget
QString createFilterExpression() const;
//! constraints management
void updateAllConstaints();
void updateConstraints( QgsEditorWidgetWrapper *w );
bool currentFormFeature( QgsFeature &feature );
bool currentFormValidConstraints( QStringList &invalidFields, QStringList &descriptions );
void constraintDependencies( QgsEditorWidgetWrapper *w, QList<QgsEditorWidgetWrapper*> &wDeps );
void clearInvalidConstraintsMessage();
void displayInvalidConstraintMessage( const QStringList &invalidFields,
const QStringList &description );
QgsVectorLayer* mLayer;
QgsFeature mFeature;
QgsMessageBar* mMessageBar;
QgsMessageBarItem* mMultiEditUnsavedMessageBarItem;
QgsMessageBarItem* mMultiEditMessageBarItem;
QLabel* mInvalidConstraintMessage;
QList<QgsWidgetWrapper*> mWidgets;
QgsAttributeEditorContext mContext;
QDialogButtonBox* mButtonBox;
@ -330,7 +343,11 @@ class GUI_EXPORT QgsAttributeForm : public QWidget
Mode mMode;
//! Backlinks widgets to buddies.
QMap<QWidget*, QLabel*> mBuddyMap;
friend class TestQgsDualView;
friend class TestQgsAttributeForm;
};
#endif // QGSATTRIBUTEFORM_H

View File

@ -14,19 +14,6 @@
<string>Edit Widget Properties</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="6" column="1">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QStackedWidget" name="stackedWidget"/>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="isFieldEditableCheckBox">
<property name="text">
@ -47,11 +34,72 @@
</property>
</widget>
</item>
<item row="0" column="0" rowspan="7">
<item row="0" column="0" rowspan="11">
<widget class="QListWidget" name="selectionListWidget"/>
</item>
<item row="10" column="1">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QStackedWidget" name="stackedWidget"/>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="notNullCheckBox">
<property name="text">
<string>Not Null</string>
</property>
</widget>
</item>
<item row="4" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="topMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Constraint</string>
</property>
</widget>
</item>
<item>
<widget class="QgsFieldExpressionWidget" name="constraintExpression" native="true"/>
</item>
</layout>
</item>
<item row="5" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Constraint description</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="constraintExpressionDescription"/>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QgsFieldExpressionWidget</class>
<extends>QWidget</extends>
<header>qgsfieldexpressionwidget.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>selectionListWidget</tabstop>
<tabstop>isFieldEditableCheckBox</tabstop>

View File

@ -5,7 +5,7 @@ SET (util_SRCS)
#####################################################
# Don't forget to include output directory, otherwise
# the UI file won't be wrapped!
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_CURRENT_BINARY_DIR}/../../../src/ui
${CMAKE_CURRENT_SOURCE_DIR}/../core #for render checker class
@ -62,7 +62,7 @@ ENDIF (APPLE)
#qtests in the executable file list as the moc is
#directly included in the sources
#and should not be compiled twice. Trying to include
#them in will cause an error at build time
#them in will cause an error at build time
#############################################################
# Tests:
@ -76,7 +76,7 @@ ENDIF (APPLE)
#ADD_EXECUTABLE(qgis_quickprinttest ${qgis_quickprinttest_SRCS})
#ADD_DEPENDENCIES(qgis_quickprinttest qgis_quickprinttestmoc)
#TARGET_LINK_LIBRARIES(qgis_quickprinttest ${QT_LIBRARIES} qgis_core qgis_gui)
#SET_TARGET_PROPERTIES(qgis_quickprinttest
#SET_TARGET_PROPERTIES(qgis_quickprinttest
# PROPERTIES INSTALL_RPATH ${QGIS_LIB_DIR}
# INSTALL_RPATH_USE_LINK_PATH true)
#IF (APPLE)
@ -128,6 +128,7 @@ ADD_QGIS_TEST(zoomtest testqgsmaptoolzoom.cpp)
#ADD_QGIS_TEST(histogramtest testqgsrasterhistogram.cpp)
ADD_QGIS_TEST(doublespinbox testqgsdoublespinbox.cpp)
ADD_QGIS_TEST(dualviewtest testqgsdualview.cpp)
ADD_QGIS_TEST(attributeformtest testqgsattributeform.cpp)
ADD_QGIS_TEST(fieldexpressionwidget testqgsfieldexpressionwidget.cpp)
ADD_QGIS_TEST(filewidget testqgsfilewidget.cpp)
ADD_QGIS_TEST(focuswatcher testqgsfocuswatcher.cpp)

View File

@ -0,0 +1,245 @@
/***************************************************************************
testqgsattributeform.cpp
--------------------------------------
Date : 13 05 2016
Copyright : (C) 2016 Paul Blottiere
Email : paul dot blottiere at oslandia dot com
***************************************************************************
* *
* 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 <QtTest/QtTest>
#include <QPushButton>
#include <editorwidgets/core/qgseditorwidgetregistry.h>
#include "qgsattributeform.h"
#include <qgsapplication.h>
#include <qgsvectorlayer.h>
#include "qgsvectordataprovider.h"
#include <qgsfeature.h>
class TestQgsAttributeForm : public QObject
{
Q_OBJECT
public:
TestQgsAttributeForm() {}
private slots:
void initTestCase(); // will be called before the first testfunction is executed.
void cleanupTestCase(); // will be called after the last testfunction was executed.
void init(); // will be called before each testfunction is executed.
void cleanup(); // will be called after every testfunction.
void testFieldConstraint();
void testFieldMultiConstraints();
void testOKButtonStatus();
};
void TestQgsAttributeForm::initTestCase()
{
QgsApplication::init();
QgsApplication::initQgis();
QgsEditorWidgetRegistry::initEditors();
}
void TestQgsAttributeForm::cleanupTestCase()
{
QgsApplication::exitQgis();
}
void TestQgsAttributeForm::init()
{
}
void TestQgsAttributeForm::cleanup()
{
}
void TestQgsAttributeForm::testFieldConstraint()
{
// make a temporary vector layer
QString def = "Point?field=col0:integer";
QgsVectorLayer* layer = new QgsVectorLayer( def, "test", "memory" );
// add a feature to the vector layer
QgsFeature ft( layer->dataProvider()->fields(), 1 );
ft.setAttribute( "col0", 0 );
// build a form for this feature
QgsAttributeForm form( layer );
form.setFeature( ft );
// testing stuff
QSignalSpy spy( &form, SIGNAL( attributeChanged( QString, QVariant ) ) );
QString validLabel = "col0<font color=\"green\">*</font>";
QString invalidLabel = "col0<font color=\"red\">*</font>";
// set constraint
layer->editFormConfig()->setExpression( 0, QString() );
// get wrapper
QgsEditorWidgetWrapper *ww;
ww = qobject_cast<QgsEditorWidgetWrapper*>( form.mWidgets[0] );
// no constraint so we expect a label with just the field name
QLabel *label = form.mBuddyMap.value( ww->widget() );
QCOMPARE( label->text(), QString( "col0" ) );
// set a not null constraint
layer->editFormConfig()->setExpression( 0, "col0 is not null" );
// set value to 1
ww->setValue( 1 );
QCOMPARE( spy.count(), 2 );
QCOMPARE( label->text(), validLabel );
// set value to null
spy.clear();
ww->setValue( QVariant() );
QCOMPARE( spy.count(), 2 );
QCOMPARE( label->text(), invalidLabel );
// set value to 1
spy.clear();
ww->setValue( 1 );
QCOMPARE( spy.count(), 2 );
QCOMPARE( label->text(), validLabel );
}
void TestQgsAttributeForm::testFieldMultiConstraints()
{
// make a temporary layer to check through
QString def = "Point?field=col0:integer&field=col1:integer&field=col2:integer&field=col3:integer";
QgsVectorLayer* layer = new QgsVectorLayer( def, "test", "memory" );
// add features to the vector layer
QgsFeature ft( layer->dataProvider()->fields(), 1 );
ft.setAttribute( "col0", 0 );
ft.setAttribute( "col1", 1 );
ft.setAttribute( "col2", 2 );
ft.setAttribute( "col3", 3 );
// set constraints for each field
layer->editFormConfig()->setExpression( 0, QString() );
layer->editFormConfig()->setExpression( 1, QString() );
layer->editFormConfig()->setExpression( 2, QString() );
layer->editFormConfig()->setExpression( 3, QString() );
// build a form for this feature
QgsAttributeForm form( layer );
form.setFeature( ft );
// testing stuff
QSignalSpy spy( &form, SIGNAL( attributeChanged( QString, QVariant ) ) );
QString val = "<font color=\"green\">*</font>";
QString inv = "<font color=\"red\">*</font>";
// get wrappers for each widget
QgsEditorWidgetWrapper *ww0, *ww1, *ww2, *ww3;
ww0 = qobject_cast<QgsEditorWidgetWrapper*>( form.mWidgets[0] );
ww1 = qobject_cast<QgsEditorWidgetWrapper*>( form.mWidgets[1] );
ww2 = qobject_cast<QgsEditorWidgetWrapper*>( form.mWidgets[2] );
ww3 = qobject_cast<QgsEditorWidgetWrapper*>( form.mWidgets[3] );
// get label for wrappers
QLabel *label0 = form.mBuddyMap.value( ww0->widget() );
QLabel *label1 = form.mBuddyMap.value( ww1->widget() );
QLabel *label2 = form.mBuddyMap.value( ww2->widget() );
QLabel *label3 = form.mBuddyMap.value( ww3->widget() );
// no constraint so we expect a label with just the field name
QCOMPARE( label0->text(), QString( "col0" ) );
QCOMPARE( label1->text(), QString( "col1" ) );
QCOMPARE( label2->text(), QString( "col2" ) );
QCOMPARE( label3->text(), QString( "col3" ) );
// update constraint
layer->editFormConfig()->setExpression( 0, "col0 < (col1 * col2)" );
layer->editFormConfig()->setExpression( 1, QString() );
layer->editFormConfig()->setExpression( 2, QString() );
layer->editFormConfig()->setExpression( 3, "col0 = 2" );
// change value
ww0->setValue( 2 ); // update col0
QCOMPARE( spy.count(), 2 );
QCOMPARE( label0->text(), QString( "col0" + inv ) ); // 2 < ( 1 + 2 )
QCOMPARE( label1->text(), QString( "col1" ) );
QCOMPARE( label2->text(), QString( "col2" ) );
QCOMPARE( label3->text(), QString( "col3" + val ) ); // 2 = 2
// change value
spy.clear();
ww0->setValue( 1 ); // update col0
QCOMPARE( spy.count(), 2 );
QCOMPARE( label0->text(), QString( "col0" + val ) ); // 1 < ( 1 + 2 )
QCOMPARE( label1->text(), QString( "col1" ) );
QCOMPARE( label2->text(), QString( "col2" ) );
QCOMPARE( label3->text(), QString( "col3" + inv ) ); // 2 = 1
}
void TestQgsAttributeForm::testOKButtonStatus()
{
// make a temporary vector layer
QString def = "Point?field=col0:integer";
QgsVectorLayer* layer = new QgsVectorLayer( def, "test", "memory" );
// add a feature to the vector layer
QgsFeature ft( layer->dataProvider()->fields(), 1 );
ft.setAttribute( "col0", 0 );
ft.setValid( true );
// build a form for this feature
QgsAttributeForm form( layer );
form.setFeature( ft );
QPushButton *okButton = form.mButtonBox->button( QDialogButtonBox::Ok );
// get wrapper
QgsEditorWidgetWrapper *ww;
ww = qobject_cast<QgsEditorWidgetWrapper*>( form.mWidgets[0] );
// testing stuff
QSignalSpy spy1( &form, SIGNAL( attributeChanged( QString, QVariant ) ) );
QSignalSpy spy2( layer, SIGNAL( editingStarted() ) );
QSignalSpy spy3( layer, SIGNAL( editingStopped() ) );
// set constraint
layer->editFormConfig()->setExpression( 0, QString() );
// no constraint but layer not editable : OK button disabled
QCOMPARE( layer->isEditable(), false );
QCOMPARE( okButton->isEnabled(), false );
// no constraint and editable layer : OK button enabled
layer->startEditing();
QCOMPARE( spy2.count(), 1 );
QCOMPARE( layer->isEditable(), true );
QCOMPARE( okButton->isEnabled(), true );
// invalid constraint and editable layer : OK button disabled
layer->editFormConfig()->setExpression( 0, "col0 = 0" );
ww->setValue( 1 );
QCOMPARE( okButton->isEnabled(), false );
// valid constraint and editable layer : OK button enabled
layer->editFormConfig()->setExpression( 0, "col0 = 2" );
ww->setValue( 2 );
QCOMPARE( okButton->isEnabled(), true );
// valid constraint and not editable layer : OK button disabled
layer->rollBack();
QCOMPARE( spy3.count(), 1 );
QCOMPARE( layer->isEditable(), false );
QCOMPARE( okButton->isEnabled(), false );
}
QTEST_MAIN( TestQgsAttributeForm )
#include "testqgsattributeform.moc"