From 4ae1b555663059fc885049c6f288921e883219a1 Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Mon, 14 Mar 2016 12:15:04 +0100 Subject: [PATCH 01/15] Add not null constraint and visual feedback to widgets --- python/core/qgseditformconfig.sip | 11 ++- python/core/qgsfield.sip | 2 +- src/app/qgsattributetypedialog.cpp | 14 ++- src/app/qgsattributetypedialog.h | 16 +++- src/app/qgsfieldsproperties.cpp | 5 + src/app/qgsfieldsproperties.h | 1 + src/core/qgseditformconfig.cpp | 16 +++- src/core/qgseditformconfig.h | 10 ++ src/core/qgseditorwidgetconfig.h | 6 +- src/core/qgsfeature.cpp | 6 ++ src/core/qgsvectorlayer.cpp | 4 +- src/core/qgsvectorlayer.h | 4 +- .../core/qgseditorwidgetregistry.cpp | 2 + .../core/qgseditorwidgetwrapper.cpp | 27 ++++++ .../core/qgseditorwidgetwrapper.h | 27 ++++++ .../editorwidgets/qgsrangewidgetfactory.cpp | 2 +- .../qgsrelationreferenceconfigdlg.cpp | 2 +- .../qgsrelationreferencefactory.cpp | 2 +- src/gui/qgsattributeform.cpp | 91 ++++++++++++++++++- src/gui/qgsattributeform.h | 7 +- src/ui/qgsattributetypeedit.ui | 31 ++++--- 21 files changed, 254 insertions(+), 32 deletions(-) diff --git a/python/core/qgseditformconfig.sip b/python/core/qgseditformconfig.sip index 06ecdc39d647..4148ac1127d5 100644 --- a/python/core/qgseditformconfig.sip +++ b/python/core/qgseditformconfig.sip @@ -474,7 +474,16 @@ 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 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 diff --git a/python/core/qgsfield.sip b/python/core/qgsfield.sip index ce63bdc41f2d..a1bd42eef03f 100644 --- a/python/core/qgsfield.sip +++ b/python/core/qgsfield.sip @@ -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. diff --git a/src/app/qgsattributetypedialog.cpp b/src/app/qgsattributetypedialog.cpp index 1d45831f3e44..0ed4bef46a84 100644 --- a/src/app/qgsattributetypedialog.cpp +++ b/src/app/qgsattributetypedialog.cpp @@ -163,16 +163,26 @@ 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(); } +bool QgsAttributeTypeDialog::notNull() const +{ + return notNullCheckBox->isChecked(); +} + void QgsAttributeTypeDialog::setFieldEditable( bool editable ) { isFieldEditableCheckBox->setChecked( editable ); diff --git a/src/app/qgsattributetypedialog.h b/src/app/qgsattributetypedialog.h index 578d2a003191..6ed6d73a7536 100644 --- a/src/app/qgsattributetypedialog.h +++ b/src/app/qgsattributetypedialog.h @@ -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,17 @@ 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 + * Getter for checkbox for not null + */ + void setNotNull( bool notnull ); + + /** + * Getter for checkbox for not null */ - bool labelOnTop(); + bool notNull() const; private slots: /** diff --git a/src/app/qgsfieldsproperties.cpp b/src/app/qgsfieldsproperties.cpp index 9278c5581c66..03c38ad51a57 100644 --- a/src/app/qgsfieldsproperties.cpp +++ b/src/app/qgsfieldsproperties.cpp @@ -527,6 +527,7 @@ void QgsFieldsProperties::attributeTypeDialog() attributeTypeDialog.setFieldEditable( cfg.mEditable ); attributeTypeDialog.setLabelOnTop( cfg.mLabelOnTop ); + attributeTypeDialog.setNotNull( cfg.mNotNull ); attributeTypeDialog.setWidgetV2Config( cfg.mEditorWidgetV2Config ); attributeTypeDialog.setWidgetV2Type( cfg.mEditorWidgetV2Type ); @@ -536,6 +537,7 @@ void QgsFieldsProperties::attributeTypeDialog() cfg.mEditable = attributeTypeDialog.fieldEditable(); cfg.mLabelOnTop = attributeTypeDialog.labelOnTop(); + cfg.mNotNull = attributeTypeDialog.notNull(); cfg.mEditorWidgetV2Type = attributeTypeDialog.editorWidgetV2Type(); cfg.mEditorWidgetV2Config = attributeTypeDialog.editorWidgetV2Config(); @@ -908,6 +910,7 @@ void QgsFieldsProperties::apply() mLayer->editFormConfig()->setReadOnly( i, !cfg.mEditable ); mLayer->editFormConfig()->setLabelOnTop( i, cfg.mLabelOnTop ); + mLayer->editFormConfig()->setNotNull( i, cfg.mNotNull ); mLayer->editFormConfig()->setWidgetType( idx, cfg.mEditorWidgetV2Type ); mLayer->editFormConfig()->setWidgetConfig( idx, cfg.mEditorWidgetV2Config ); @@ -974,6 +977,7 @@ QgsFieldsProperties::FieldConfig::FieldConfig() : mEditable( true ) , mEditableEnabled( true ) , mLabelOnTop( false ) + , mNotNull( false ) , mButton( nullptr ) { } @@ -985,6 +989,7 @@ 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 ); mEditorWidgetV2Type = layer->editFormConfig()->widgetType( idx ); mEditorWidgetV2Config = layer->editFormConfig()->widgetConfig( idx ); diff --git a/src/app/qgsfieldsproperties.h b/src/app/qgsfieldsproperties.h index 2189e862a212..ae53b3d0bf1b 100644 --- a/src/app/qgsfieldsproperties.h +++ b/src/app/qgsfieldsproperties.h @@ -92,6 +92,7 @@ class APP_EXPORT QgsFieldsProperties : public QWidget, private Ui_QgsFieldsPrope bool mEditable; bool mEditableEnabled; bool mLabelOnTop; + bool mNotNull; QPushButton* mButton; QString mEditorWidgetV2Type; QMap mEditorWidgetV2Config; diff --git a/src/core/qgseditformconfig.cpp b/src/core/qgseditformconfig.cpp index 4c9279010655..e98deda8afda 100644 --- a/src/core/qgseditformconfig.cpp +++ b/src/core/qgseditformconfig.cpp @@ -119,6 +119,14 @@ bool QgsEditFormConfig::labelOnTop( int idx ) const return false; } +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 +139,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 +294,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 +350,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 ); diff --git a/src/core/qgseditformconfig.h b/src/core/qgseditformconfig.h index 65e8689f6bb4..f34e7100859d 100644 --- a/src/core/qgseditformconfig.h +++ b/src/core/qgseditformconfig.h @@ -512,6 +512,15 @@ class CORE_EXPORT QgsEditFormConfig : public QObject */ void setReadOnly( int idx, bool readOnly = true ); + /** + * 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. @@ -633,6 +642,7 @@ class CORE_EXPORT QgsEditFormConfig : public QObject QMap< QString, bool> mFieldEditables; QMap< QString, bool> mLabelOnTop; + QMap< QString, bool> mNotNull; QMap mEditorWidgetV2Types; QMap mWidgetConfigs; diff --git a/src/core/qgseditorwidgetconfig.h b/src/core/qgseditorwidgetconfig.h index a6dbceefb019..b9fdabdb4ad1 100644 --- a/src/core/qgseditorwidgetconfig.h +++ b/src/core/qgseditorwidgetconfig.h @@ -16,6 +16,8 @@ #include #include +#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 QgsEditorWidgetConfig; +typedef QVariantMap QgsEditorWidgetConfig; + +#endif // QGSEDITORWIDGETCONFIG_H diff --git a/src/core/qgsfeature.cpp b/src/core/qgsfeature.cpp index 0cef3ee28691..c71ff8241f1a 100644 --- a/src/core/qgsfeature.cpp +++ b/src/core/qgsfeature.cpp @@ -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(); diff --git a/src/core/qgsvectorlayer.cpp b/src/core/qgsvectorlayer.cpp index e1fa901b6947..b025b4aa5af2 100644 --- a/src/core/qgsvectorlayer.cpp +++ b/src/core/qgsvectorlayer.cpp @@ -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(); diff --git a/src/core/qgsvectorlayer.h b/src/core/qgsvectorlayer.h index ee902cb6ef53..3adb15401af7 100644 --- a/src/core/qgsvectorlayer.h +++ b/src/core/qgsvectorlayer.h @@ -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 diff --git a/src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp b/src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp index 63184a75620c..9dc1116157e6 100644 --- a/src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp +++ b/src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp @@ -251,6 +251,7 @@ 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()->setWidgetConfig( idx, cfg ); } else @@ -307,6 +308,7 @@ 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 ) ); mWidgetFactories[widgetType]->writeConfig( vectorLayer->editFormConfig()->widgetConfig( idx ), ewv2CfgElem, doc, vectorLayer, idx ); diff --git a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp index 79e23a8a7ce0..403795f243b0 100644 --- a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp +++ b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp @@ -24,6 +24,7 @@ QgsEditorWidgetWrapper::QgsEditorWidgetWrapper( QgsVectorLayer* vl, int fieldIdx : QgsWidgetWrapper( vl, editor, parent ) , mFieldIdx( fieldIdx ) { + connect( this, SIGNAL( valueChanged( QVariant ) ), this, SLOT( onValueChanged( QVariant ) ) ); } int QgsEditorWidgetWrapper::fieldIdx() const @@ -61,6 +62,7 @@ void QgsEditorWidgetWrapper::setEnabled( bool enabled ) void QgsEditorWidgetWrapper::setFeature( const QgsFeature& feature ) { setValue( feature.attribute( mFieldIdx ) ); + onValueChanged( value() ); } void QgsEditorWidgetWrapper::valueChanged( const QString& value ) @@ -92,3 +94,28 @@ void QgsEditorWidgetWrapper::valueChanged() { emit valueChanged( value() ); } + +void QgsEditorWidgetWrapper::updateConstraintsOk( bool constraintStatus ) +{ + if ( constraintStatus ) + { + widget()->setStyleSheet( "" ); + } + else + { + widget()->setStyleSheet( "QWidget{ background-color: '#dd7777': }" ); + } +} + +void QgsEditorWidgetWrapper::onValueChanged( const QVariant& value ) +{ + if ( layer()->editFormConfig()->notNull( mFieldIdx ) ) + { + if ( value.isNull() != mIsNull ) + { + updateConstraintsOk( value.isNull() ); + emit constraintStatusChanged( "NotNull", !value.isNull() ); + mIsNull = value.isNull(); + } + } +} diff --git a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h index ad30e2ad8797..182f0ecb244d 100644 --- a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h +++ b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h @@ -118,6 +118,13 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper */ void valueChanged( const QVariant& value ); + /** + * @brief constraintStatusChanged + * @param constraint + * @param status + */ + void constraintStatusChanged( const QString& constraint, bool status ); + public slots: /** * Will be called when the feature changes @@ -185,8 +192,28 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper */ void valueChanged(); + private: + /** + * 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 updateConstraintsOk( bool constraintStatus ); + + private slots: + /** + * @brief mFieldIdx + */ + void onValueChanged( const QVariant& value ); + private: int mFieldIdx; + bool mIsNull; }; // We'll use this class inside a QVariant in the widgets properties diff --git a/src/gui/editorwidgets/qgsrangewidgetfactory.cpp b/src/gui/editorwidgets/qgsrangewidgetfactory.cpp index 9ef096d5c381..dbda88b42b79 100644 --- a/src/gui/editorwidgets/qgsrangewidgetfactory.cpp +++ b/src/gui/editorwidgets/qgsrangewidgetfactory.cpp @@ -38,7 +38,7 @@ QgsEditorWidgetConfig QgsRangeWidgetFactory::readConfig( const QDomElement& conf { Q_UNUSED( layer ); Q_UNUSED( fieldIdx ); - QMap cfg; + QgsEditorWidgetConfig cfg; cfg.insert( "Style", configElement.attribute( "Style" ) ); cfg.insert( "Min", configElement.attribute( "Min" ) ); diff --git a/src/gui/editorwidgets/qgsrelationreferenceconfigdlg.cpp b/src/gui/editorwidgets/qgsrelationreferenceconfigdlg.cpp index 2c59588e075f..6b58462561d7 100644 --- a/src/gui/editorwidgets/qgsrelationreferenceconfigdlg.cpp +++ b/src/gui/editorwidgets/qgsrelationreferenceconfigdlg.cpp @@ -55,7 +55,7 @@ QgsRelationReferenceConfigDlg::QgsRelationReferenceConfigDlg( QgsVectorLayer* vl } } -void QgsRelationReferenceConfigDlg::setConfig( const QMap& config ) +void QgsRelationReferenceConfigDlg::setConfig( const QgsEditorWidgetConfig& config ) { if ( config.contains( "AllowNULL" ) ) { diff --git a/src/gui/editorwidgets/qgsrelationreferencefactory.cpp b/src/gui/editorwidgets/qgsrelationreferencefactory.cpp index 9315d14eb03a..b6cf3438535f 100644 --- a/src/gui/editorwidgets/qgsrelationreferencefactory.cpp +++ b/src/gui/editorwidgets/qgsrelationreferencefactory.cpp @@ -46,7 +46,7 @@ QgsEditorWidgetConfig QgsRelationReferenceFactory::readConfig( const QDomElement { Q_UNUSED( layer ); Q_UNUSED( fieldIdx ); - QMap cfg; + QgsEditorWidgetConfig cfg; cfg.insert( "AllowNULL", configElement.attribute( "AllowNULL" ) == "1" ); cfg.insert( "OrderByValue", configElement.attribute( "OrderByValue" ) == "1" ); diff --git a/src/gui/qgsattributeform.cpp b/src/gui/qgsattributeform.cpp index ea65e175451a..9a31e6f76d56 100644 --- a/src/gui/qgsattributeform.cpp +++ b/src/gui/qgsattributeform.cpp @@ -655,6 +655,41 @@ 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" ).arg( text ) ); +#else + buddy->setText( QString( "%1*" ).arg( text ) ); +#endif + } + else + { + // good +#if QT_VERSION >= 0x050000 + buddy->setText( QString( "%1" ).arg( text ) ); +#else + buddy->setText( QString( "%1*" ).arg( text ) ); +#endif + } + } + } + + + emit attributeChanged( eww->field().name(), value ); } void QgsAttributeForm::onAttributeAdded( int idx ) @@ -714,6 +749,36 @@ void QgsAttributeForm::onUpdatedFields() setFeature( mFeature ); } +void QgsAttributeForm::onConstraintStatusChanged( const QString& constraint, bool ok ) +{ + Q_UNUSED( constraint ) + + + QgsEditorWidgetWrapper* eww = qobject_cast( sender() ); + Q_ASSERT( eww ); + + 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 ( !ok ) + { + // not good + buddy->setText( QString( "%1*" ).arg( text ) ); + } + else + { + // good + buddy->setText( QString( "%1*" ).arg( text ) ); + } + } +} + void QgsAttributeForm::preventFeatureRefresh() { mPreventFeatureRefresh = true; @@ -899,7 +964,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; @@ -915,6 +980,11 @@ void QgsAttributeForm::init() w = new QLabel( QString( "

Failed to create widget with type '%1'

" ).arg( widgetType ) ); } + l->setBuddy( w ); + + if ( w ) + w->setObjectName( field.name() ); + if ( eww ) addWidgetWrapper( eww ); @@ -957,6 +1027,7 @@ void QgsAttributeForm::init() } mButtonBox->setVisible( buttonBoxVisible ); +<<<<<<< HEAD if ( !mSearchButtonBox ) { mSearchButtonBox = new QWidget(); @@ -1011,7 +1082,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 +1322,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 +1423,7 @@ void QgsAttributeForm::createWrappers() } } -void QgsAttributeForm::connectWrappers() +void QgsAttributeForm::afterWidgetInit() { bool isFirstEww = true; @@ -1367,8 +1440,20 @@ void QgsAttributeForm::connectWrappers() } connect( eww, SIGNAL( valueChanged( const QVariant& ) ), this, SLOT( onAttributeChanged( const QVariant& ) ) ); + connect( eww, SIGNAL( constraintStatusChanged( QString, bool ) ), this, SLOT( onConstraintStatusChanged( QString, bool ) ) ); } } + + // Update buddy widget list + + mBuddyMap.clear(); + QList labels = findChildren(); + + Q_FOREACH ( QLabel* label, labels ) + { + if ( label->buddy() ) + mBuddyMap.insert( label->buddy(), label ); + } } diff --git a/src/gui/qgsattributeform.h b/src/gui/qgsattributeform.h index 89770ebc2886..a49c6546a28a 100644 --- a/src/gui/qgsattributeform.h +++ b/src/gui/qgsattributeform.h @@ -21,6 +21,7 @@ #include "qgsattributeeditorcontext.h" #include +#include #include class QgsAttributeFormInterface; @@ -233,6 +234,7 @@ class GUI_EXPORT QgsAttributeForm : public QWidget void onAttributeAdded( int idx ); void onAttributeDeleted( int idx ); void onUpdatedFields(); + void onConstraintStatusChanged( const QString& constraint, bool ok ); void preventFeatureRefresh(); void synchronizeEnabledState(); @@ -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; @@ -330,6 +332,9 @@ class GUI_EXPORT QgsAttributeForm : public QWidget Mode mMode; + //! Backlinks widgets to buddies. + QMap mBuddyMap; + friend class TestQgsDualView; }; diff --git a/src/ui/qgsattributetypeedit.ui b/src/ui/qgsattributetypeedit.ui index 5f774a4adb82..adb2cb02bd8b 100644 --- a/src/ui/qgsattributetypeedit.ui +++ b/src/ui/qgsattributetypeedit.ui @@ -14,17 +14,7 @@ Edit Widget Properties - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - + @@ -47,9 +37,26 @@ - + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + Not Null + + + From 020d20a968de9d38f5a622383c7404d8777e8088 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Mon, 9 May 2016 14:46:51 +0200 Subject: [PATCH 02/15] [FEATURE] constraints on widgets --- python/core/qgseditformconfig.sip | 16 ++ .../core/qgseditorwidgetwrapper.sip | 35 +++ .../qgsrelationreferencewidgetwrapper.sip | 14 + src/app/qgsattributetypedialog.cpp | 12 + src/app/qgsattributetypedialog.h | 12 + src/app/qgsfieldsproperties.cpp | 4 + src/app/qgsfieldsproperties.h | 1 + src/core/qgseditformconfig.cpp | 16 ++ src/core/qgseditformconfig.h | 17 ++ .../core/qgseditorwidgetregistry.cpp | 3 + .../core/qgseditorwidgetwrapper.cpp | 70 ++++- .../core/qgseditorwidgetwrapper.h | 34 ++- .../editorwidgets/qgscolorwidgetwrapper.cpp | 5 + src/gui/editorwidgets/qgscolorwidgetwrapper.h | 2 + .../qgsexternalresourcewidgetwrapper.cpp | 18 +- .../qgsexternalresourcewidgetwrapper.h | 2 + .../qgsfilenamewidgetwrapper.cpp | 13 + .../editorwidgets/qgsfilenamewidgetwrapper.h | 2 + .../editorwidgets/qgsphotowidgetwrapper.cpp | 13 + src/gui/editorwidgets/qgsphotowidgetwrapper.h | 2 + .../qgsrelationreferencewidgetwrapper.cpp | 11 + .../qgsrelationreferencewidgetwrapper.h | 14 + .../editorwidgets/qgswebviewwidgetwrapper.cpp | 13 + .../editorwidgets/qgswebviewwidgetwrapper.h | 2 + src/gui/qgsattributeform.cpp | 191 +++++++++++++- src/gui/qgsattributeform.h | 15 +- src/ui/qgsattributetypeedit.ui | 34 ++- tests/src/gui/CMakeLists.txt | 7 +- tests/src/gui/testqgsattributeform.cpp | 245 ++++++++++++++++++ 29 files changed, 782 insertions(+), 41 deletions(-) create mode 100644 tests/src/gui/testqgsattributeform.cpp diff --git a/python/core/qgseditformconfig.sip b/python/core/qgseditformconfig.sip index 4148ac1127d5..f776fd62d926 100644 --- a/python/core/qgseditformconfig.sip +++ b/python/core/qgseditformconfig.sip @@ -476,6 +476,22 @@ class QgsEditFormConfig : 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 constraint( 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 setConstraint( int idx, const QString& str ); + /** * Returns if the field at fieldidx should be treated as NOT NULL value */ diff --git a/python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip b/python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip index 680aa76c196b..41634a01bac9 100644 --- a/python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip +++ b/python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip @@ -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,13 @@ class QgsEditorWidgetWrapper : QgsWidgetWrapper */ void valueChanged( const QVariant& value ); + /** + * @brief constraintStatusChanged + * @param constraint + * @param status + */ + void constraintStatusChanged( const QString& constraint, const QString& err, bool status ); + public slots: /** * Will be called when the feature changes @@ -162,4 +184,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(); }; diff --git a/python/gui/editorwidgets/qgsrelationreferencewidgetwrapper.sip b/python/gui/editorwidgets/qgsrelationreferencewidgetwrapper.sip index 41414b019d5f..3fc1ec34ffb7 100644 --- a/python/gui/editorwidgets/qgsrelationreferencewidgetwrapper.sip +++ b/python/gui/editorwidgets/qgsrelationreferencewidgetwrapper.sip @@ -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(); }; diff --git a/src/app/qgsattributetypedialog.cpp b/src/app/qgsattributetypedialog.cpp index 0ed4bef46a84..2d7b09d608c2 100644 --- a/src/app/qgsattributetypedialog.cpp +++ b/src/app/qgsattributetypedialog.cpp @@ -71,6 +71,8 @@ QgsAttributeTypeDialog::QgsAttributeTypeDialog( QgsVectorLayer *vl, int fieldIdx QSettings settings; restoreGeometry( settings.value( "/Windows/QgsAttributeTypeDialog/geometry" ).toByteArray() ); + + constraintExpression->setLayer( vl ); } QgsAttributeTypeDialog::~QgsAttributeTypeDialog() @@ -183,6 +185,16 @@ bool QgsAttributeTypeDialog::notNull() const return notNullCheckBox->isChecked(); } +void QgsAttributeTypeDialog::setConstraint( const QString &str ) +{ + constraintExpression->setField( str ); +} + +QString QgsAttributeTypeDialog::constraint() const +{ + return constraintExpression->asExpression(); +} + void QgsAttributeTypeDialog::setFieldEditable( bool editable ) { isFieldEditableCheckBox->setChecked( editable ); diff --git a/src/app/qgsattributetypedialog.h b/src/app/qgsattributetypedialog.h index 6ed6d73a7536..b7b3b43ef3a2 100644 --- a/src/app/qgsattributetypedialog.h +++ b/src/app/qgsattributetypedialog.h @@ -94,6 +94,18 @@ class APP_EXPORT QgsAttributeTypeDialog: public QDialog, private Ui::QgsAttribut */ bool notNull() const; + /** + * Getter for the constraint expression + * @note added in QGIS 2.16 + */ + QString constraint() const; + + /** + * Setter for the constraint expression + * @note added in QGIS 2.16 + */ + void setConstraint( const QString &str ); + private slots: /** * Slot to handle change of index in combobox to select correct page diff --git a/src/app/qgsfieldsproperties.cpp b/src/app/qgsfieldsproperties.cpp index 03c38ad51a57..92015291070c 100644 --- a/src/app/qgsfieldsproperties.cpp +++ b/src/app/qgsfieldsproperties.cpp @@ -528,6 +528,7 @@ void QgsFieldsProperties::attributeTypeDialog() attributeTypeDialog.setFieldEditable( cfg.mEditable ); attributeTypeDialog.setLabelOnTop( cfg.mLabelOnTop ); attributeTypeDialog.setNotNull( cfg.mNotNull ); + attributeTypeDialog.setConstraint( cfg.mConstraint ); attributeTypeDialog.setWidgetV2Config( cfg.mEditorWidgetV2Config ); attributeTypeDialog.setWidgetV2Type( cfg.mEditorWidgetV2Type ); @@ -538,6 +539,7 @@ void QgsFieldsProperties::attributeTypeDialog() cfg.mEditable = attributeTypeDialog.fieldEditable(); cfg.mLabelOnTop = attributeTypeDialog.labelOnTop(); cfg.mNotNull = attributeTypeDialog.notNull(); + cfg.mConstraint = attributeTypeDialog.constraint(); cfg.mEditorWidgetV2Type = attributeTypeDialog.editorWidgetV2Type(); cfg.mEditorWidgetV2Config = attributeTypeDialog.editorWidgetV2Config(); @@ -911,6 +913,7 @@ void QgsFieldsProperties::apply() mLayer->editFormConfig()->setReadOnly( i, !cfg.mEditable ); mLayer->editFormConfig()->setLabelOnTop( i, cfg.mLabelOnTop ); mLayer->editFormConfig()->setNotNull( i, cfg.mNotNull ); + mLayer->editFormConfig()->setConstraint( i, cfg.mConstraint ); mLayer->editFormConfig()->setWidgetType( idx, cfg.mEditorWidgetV2Type ); mLayer->editFormConfig()->setWidgetConfig( idx, cfg.mEditorWidgetV2Config ); @@ -990,6 +993,7 @@ QgsFieldsProperties::FieldConfig::FieldConfig( QgsVectorLayer* layer, int idx ) && layer->fields().fieldOrigin( idx ) != QgsFields::OriginExpression; mLabelOnTop = layer->editFormConfig()->labelOnTop( idx ); mNotNull = layer->editFormConfig()->notNull( idx ); + mConstraint = layer->editFormConfig()->constraint( idx ); mEditorWidgetV2Type = layer->editFormConfig()->widgetType( idx ); mEditorWidgetV2Config = layer->editFormConfig()->widgetConfig( idx ); diff --git a/src/app/qgsfieldsproperties.h b/src/app/qgsfieldsproperties.h index ae53b3d0bf1b..1d982c99111b 100644 --- a/src/app/qgsfieldsproperties.h +++ b/src/app/qgsfieldsproperties.h @@ -93,6 +93,7 @@ class APP_EXPORT QgsFieldsProperties : public QWidget, private Ui_QgsFieldsPrope bool mEditableEnabled; bool mLabelOnTop; bool mNotNull; + QString mConstraint; QPushButton* mButton; QString mEditorWidgetV2Type; QMap mEditorWidgetV2Config; diff --git a/src/core/qgseditformconfig.cpp b/src/core/qgseditformconfig.cpp index e98deda8afda..1311624d1b8b 100644 --- a/src/core/qgseditformconfig.cpp +++ b/src/core/qgseditformconfig.cpp @@ -119,6 +119,22 @@ bool QgsEditFormConfig::labelOnTop( int idx ) const return false; } +QString QgsEditFormConfig::constraint( int idx ) const +{ + QString expr = ""; + + if ( idx >= 0 && idx < mFields.count() ) + expr = mConstraints.value( mFields.at( idx ).name(), "" ); + + return expr; +} + +void QgsEditFormConfig::setConstraint( int idx, const QString& str ) +{ + if ( idx >= 0 && idx < mFields.count() ) + mConstraints[ mFields.at( idx ).name()] = str; +} + bool QgsEditFormConfig::notNull( int idx ) const { if ( idx >= 0 && idx < mFields.count() ) diff --git a/src/core/qgseditformconfig.h b/src/core/qgseditformconfig.h index f34e7100859d..204600fc7620 100644 --- a/src/core/qgseditformconfig.h +++ b/src/core/qgseditformconfig.h @@ -512,6 +512,22 @@ 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 constraint( 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 setConstraint( int idx, const QString& str ); + /** * Returns if the field at fieldidx should be treated as NOT NULL value */ @@ -640,6 +656,7 @@ 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, bool> mFieldEditables; QMap< QString, bool> mLabelOnTop; QMap< QString, bool> mNotNull; diff --git a/src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp b/src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp index 9dc1116157e6..d6d9eef4368d 100644 --- a/src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp +++ b/src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp @@ -252,6 +252,8 @@ 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()->setConstraint( idx, ewv2CfgElem.attribute( "constraint", "" ) ); + vectorLayer->editFormConfig()->setWidgetConfig( idx, cfg ); } else @@ -309,6 +311,7 @@ void QgsEditorWidgetRegistry::writeMapLayer( QgsMapLayer* mapLayer, QDomElement& 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()->constraint( idx ) ); mWidgetFactories[widgetType]->writeConfig( vectorLayer->editFormConfig()->widgetConfig( idx ), ewv2CfgElem, doc, vectorLayer, idx ); diff --git a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp index 403795f243b0..ef9959ad7662 100644 --- a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp +++ b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp @@ -22,9 +22,9 @@ QgsEditorWidgetWrapper::QgsEditorWidgetWrapper( QgsVectorLayer* vl, int fieldIdx, QWidget* editor, QWidget* parent ) : QgsWidgetWrapper( vl, editor, parent ) + , mValidConstraint( true ) , mFieldIdx( fieldIdx ) { - connect( this, SIGNAL( valueChanged( QVariant ) ), this, SLOT( onValueChanged( QVariant ) ) ); } int QgsEditorWidgetWrapper::fieldIdx() const @@ -61,8 +61,8 @@ void QgsEditorWidgetWrapper::setEnabled( bool enabled ) void QgsEditorWidgetWrapper::setFeature( const QgsFeature& feature ) { + mFeature = feature; setValue( feature.attribute( mFieldIdx ) ); - onValueChanged( value() ); } void QgsEditorWidgetWrapper::valueChanged( const QString& value ) @@ -95,27 +95,69 @@ void QgsEditorWidgetWrapper::valueChanged() emit valueChanged( value() ); } -void QgsEditorWidgetWrapper::updateConstraintsOk( bool constraintStatus ) +void QgsEditorWidgetWrapper::updateConstraintWidgetStatus() { - if ( constraintStatus ) - { + if ( mValidConstraint ) widget()->setStyleSheet( "" ); - } else - { - widget()->setStyleSheet( "QWidget{ background-color: '#dd7777': }" ); - } + widget()->setStyleSheet( "background-color: #dd7777;" ); } -void QgsEditorWidgetWrapper::onValueChanged( const QVariant& value ) +void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft ) { + bool toEmit( false ); + QString errStr( "predicate is True" ); + QString expression = layer()->editFormConfig()->constraint( mFieldIdx ); + QVariant value = ft.attribute( mFieldIdx ); + + if ( ! expression.isEmpty() ) + { + QgsExpressionContext context = + QgsExpressionContextUtils::createFeatureBasedContext( ft, *ft.fields() ); + + 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 = "predicate is False"; + + toEmit = true; + } + else + mValidConstraint = true; + if ( layer()->editFormConfig()->notNull( mFieldIdx ) ) { - if ( value.isNull() != mIsNull ) + if ( !expression.isEmpty() ) { - updateConstraintsOk( value.isNull() ); - emit constraintStatusChanged( "NotNull", !value.isNull() ); - mIsNull = value.isNull(); + QString fieldName = ft.fields()->field( mFieldIdx ).name(); + expression = "( " + expression + " ) AND ( " + fieldName + " IS NOT NULL)"; } + else + expression = "NotNull"; + + mValidConstraint = mValidConstraint && !value.isNull(); + + if ( value.isNull() ) + errStr = "predicate is False"; + + toEmit = true; } + + if ( toEmit ) + { + updateConstraintWidgetStatus(); + emit constraintStatusChanged( expression, errStr, mValidConstraint ); + } +} + +bool QgsEditorWidgetWrapper::isValidConstraint() const +{ + return mValidConstraint; } diff --git a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h index 182f0ecb244d..57e7274f1985 100644 --- a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h +++ b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h @@ -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. @@ -119,11 +134,13 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper void valueChanged( const QVariant& value ); /** + * Emit this signal when the constraint status changed. * @brief constraintStatusChanged - * @param constraint + * @param constraint represented as a string + * @param err the error represented as a string. Empty if none. * @param status */ - void constraintStatusChanged( const QString& constraint, bool status ); + void constraintStatusChanged( const QString& constraint, const QString& err, bool status ); public slots: /** @@ -192,7 +209,7 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper */ void valueChanged(); - private: + protected: /** * This should update the widget with a visual cue if a constraint status * changed. @@ -202,18 +219,15 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper * * This can be overwritten in subclasses to allow individual widgets to * change the visual cue. + * @note added in QGIS 2.16 */ - virtual void updateConstraintsOk( bool constraintStatus ); + virtual void updateConstraintWidgetStatus(); - private slots: - /** - * @brief mFieldIdx - */ - void onValueChanged( const QVariant& value ); + bool mValidConstraint; private: int mFieldIdx; - bool mIsNull; + QgsFeature mFeature; }; // We'll use this class inside a QVariant in the widgets properties diff --git a/src/gui/editorwidgets/qgscolorwidgetwrapper.cpp b/src/gui/editorwidgets/qgscolorwidgetwrapper.cpp index 0bee7fab9608..724a94d11b99 100644 --- a/src/gui/editorwidgets/qgscolorwidgetwrapper.cpp +++ b/src/gui/editorwidgets/qgscolorwidgetwrapper.cpp @@ -77,3 +77,8 @@ void QgsColorWidgetWrapper::setValue( const QVariant& value ) if ( mColorButton ) mColorButton->setColor( !value.isNull() ? QColor( value.toString() ) : QColor() ); } + +void QgsColorWidgetWrapper::updateConstraintWidgetStatus() +{ + // nothing +} diff --git a/src/gui/editorwidgets/qgscolorwidgetwrapper.h b/src/gui/editorwidgets/qgscolorwidgetwrapper.h index d6acbb4f2ea8..4876d85ca46f 100644 --- a/src/gui/editorwidgets/qgscolorwidgetwrapper.h +++ b/src/gui/editorwidgets/qgscolorwidgetwrapper.h @@ -46,6 +46,8 @@ class GUI_EXPORT QgsColorWidgetWrapper : public QgsEditorWidgetWrapper void setValue( const QVariant& value ) override; private: + void updateConstraintWidgetStatus() override; + QgsColorButtonV2* mColorButton; }; diff --git a/src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.cpp b/src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.cpp index 09b2a889a326..2c5c9f5b5c4e 100644 --- a/src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.cpp +++ b/src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.cpp @@ -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(); 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( "" ); + else + mLineEdit->setStyleSheet( "QgsFilterLineEdit { background-color: #dd7777; }" ); + } +} \ No newline at end of file diff --git a/src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.h b/src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.h index ec31ebd5fa2d..fde7d5db538b 100644 --- a/src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.h +++ b/src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.h @@ -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; diff --git a/src/gui/editorwidgets/qgsfilenamewidgetwrapper.cpp b/src/gui/editorwidgets/qgsfilenamewidgetwrapper.cpp index 3f4df5e56047..bb103db72b96 100644 --- a/src/gui/editorwidgets/qgsfilenamewidgetwrapper.cpp +++ b/src/gui/editorwidgets/qgsfilenamewidgetwrapper.cpp @@ -151,3 +151,16 @@ void QgsFileNameWidgetWrapper::selectFileName() if ( mLabel ) mLineEdit->setText( fileName ); } + +void QgsFileNameWidgetWrapper::updateConstraintWidgetStatus() +{ + if ( mLineEdit ) + { + if ( mValidConstraint ) + mLineEdit->setStyleSheet( "" ); + else + { + mLineEdit->setStyleSheet( "QgsFilterLineEdit { background-color: #dd7777; }" ); + } + } +} diff --git a/src/gui/editorwidgets/qgsfilenamewidgetwrapper.h b/src/gui/editorwidgets/qgsfilenamewidgetwrapper.h index 4ebc92b75012..caadfed5e601 100644 --- a/src/gui/editorwidgets/qgsfilenamewidgetwrapper.h +++ b/src/gui/editorwidgets/qgsfilenamewidgetwrapper.h @@ -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; diff --git a/src/gui/editorwidgets/qgsphotowidgetwrapper.cpp b/src/gui/editorwidgets/qgsphotowidgetwrapper.cpp index 3718182c765e..f48c6c802db5 100644 --- a/src/gui/editorwidgets/qgsphotowidgetwrapper.cpp +++ b/src/gui/editorwidgets/qgsphotowidgetwrapper.cpp @@ -266,3 +266,16 @@ void QgsPhotoWidgetWrapper::setEnabled( bool enabled ) if ( mButton ) mButton->setEnabled( enabled ); } + +void QgsPhotoWidgetWrapper::updateConstraintWidgetStatus() +{ + if ( mLineEdit ) + { + if ( mValidConstraint ) + mLineEdit->setStyleSheet( "" ); + else + { + mLineEdit->setStyleSheet( "QgsFilterLineEdit { background-color: #dd7777; }" ); + } + } +} diff --git a/src/gui/editorwidgets/qgsphotowidgetwrapper.h b/src/gui/editorwidgets/qgsphotowidgetwrapper.h index 7564bdb9400f..1aef55556fd5 100644 --- a/src/gui/editorwidgets/qgsphotowidgetwrapper.h +++ b/src/gui/editorwidgets/qgsphotowidgetwrapper.h @@ -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. diff --git a/src/gui/editorwidgets/qgsrelationreferencewidgetwrapper.cpp b/src/gui/editorwidgets/qgsrelationreferencewidgetwrapper.cpp index 738a4546f9e2..150c81f1de2b 100644 --- a/src/gui/editorwidgets/qgsrelationreferencewidgetwrapper.cpp +++ b/src/gui/editorwidgets/qgsrelationreferencewidgetwrapper.cpp @@ -139,3 +139,14 @@ void QgsRelationReferenceWidgetWrapper::foreignKeyChanged( QVariant value ) } emit valueChanged( value ); } + +void QgsRelationReferenceWidgetWrapper::updateConstraintWidgetStatus() +{ + if ( mWidget ) + { + if ( mValidConstraint ) + mWidget->setStyleSheet( "" ); + else + mWidget->setStyleSheet( ".QComboBox { background-color: #dd7777; }" ); + } +} \ No newline at end of file diff --git a/src/gui/editorwidgets/qgsrelationreferencewidgetwrapper.h b/src/gui/editorwidgets/qgsrelationreferencewidgetwrapper.h index d4a58f4883b0..1a171bd3a241 100644 --- a/src/gui/editorwidgets/qgsrelationreferencewidgetwrapper.h +++ b/src/gui/editorwidgets/qgsrelationreferencewidgetwrapper.h @@ -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; diff --git a/src/gui/editorwidgets/qgswebviewwidgetwrapper.cpp b/src/gui/editorwidgets/qgswebviewwidgetwrapper.cpp index 3e936e6ab97c..5fa44e72a458 100644 --- a/src/gui/editorwidgets/qgswebviewwidgetwrapper.cpp +++ b/src/gui/editorwidgets/qgswebviewwidgetwrapper.cpp @@ -188,3 +188,16 @@ void QgsWebViewWidgetWrapper::selectFileName() if ( mLineEdit ) mLineEdit->setText( filePath ); } + +void QgsWebViewWidgetWrapper::updateConstraintWidgetStatus() +{ + if ( mLineEdit ) + { + if ( mValidConstraint ) + mLineEdit->setStyleSheet( "" ); + else + { + mLineEdit->setStyleSheet( "QgsFilterLineEdit { background-color: #dd7777; }" ); + } + } +} \ No newline at end of file diff --git a/src/gui/editorwidgets/qgswebviewwidgetwrapper.h b/src/gui/editorwidgets/qgswebviewwidgetwrapper.h index 40397b7a7bc6..321de02d0f22 100644 --- a/src/gui/editorwidgets/qgswebviewwidgetwrapper.h +++ b/src/gui/editorwidgets/qgswebviewwidgetwrapper.h @@ -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 diff --git a/src/gui/qgsattributeform.cpp b/src/gui/qgsattributeform.cpp index 9a31e6f76d56..81d38ed6c08f 100644 --- a/src/gui/qgsattributeform.cpp +++ b/src/gui/qgsattributeform.cpp @@ -49,6 +49,8 @@ int QgsAttributeForm::sFormCounter = 0; QgsAttributeForm::QgsAttributeForm( QgsVectorLayer* vl, const QgsFeature &feature, const QgsAttributeEditorContext &context, QWidget* parent ) : QWidget( parent ) + , mInvalidConstraintMessageBarItem( nullptr ) + , mFieldNotInitializedMessageBarItem( nullptr ) , mLayer( vl ) , mMessageBar( nullptr ) , mMultiEditUnsavedMessageBarItem( nullptr ) @@ -79,6 +81,10 @@ 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 + displayNullFieldsMessage(); + updateAllConstaints(); } QgsAttributeForm::~QgsAttributeForm() @@ -688,10 +694,137 @@ void QgsAttributeForm::onAttributeChanged( const QVariant& value ) } } + updateConstraints( eww ); + // emit emit attributeChanged( eww->field().name(), value ); } +void QgsAttributeForm::updateAllConstaints() +{ + Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets ) + { + QgsEditorWidgetWrapper* eww = qobject_cast( 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 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( 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::displayNullFieldsMessage() +{ + QStringList notInitializedFields; + Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets ) + { + QgsEditorWidgetWrapper* eww = qobject_cast( ww ); + if ( eww ) + { + if ( mFeature.attribute( eww->fieldIdx() ).isNull() ) + notInitializedFields.append( eww->field().name() ); + } + } + + if ( ! notInitializedFields.isEmpty() ) + { + mFieldNotInitializedMessageBarItem = + new QgsMessageBarItem( tr( "Some fields are NULL: " ), + notInitializedFields.join( ", " ), + QgsMessageBar::INFO ); + mMessageBar->pushItem( mFieldNotInitializedMessageBarItem ); + } + +} + +void QgsAttributeForm::clearInvalidConstraintsMessage() +{ + if ( mInvalidConstraintMessageBarItem != nullptr ) + { + mMessageBar->popWidget( mInvalidConstraintMessageBarItem ); + mInvalidConstraintMessageBarItem = nullptr; + } +} + +void QgsAttributeForm::displayInvalidConstraintMessage( const QStringList &f ) +{ + clearInvalidConstraintsMessage(); + + mInvalidConstraintMessageBarItem = + new QgsMessageBarItem( tr( "Invalid fields:" ), + f.join( ", " ), QgsMessageBar::WARNING ); + mMessageBar->pushItem( mInvalidConstraintMessageBarItem ); +} + +bool QgsAttributeForm::currentFormValidConstraints( QStringList &invalidFields ) +{ + bool valid( true ); + + Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets ) + { + QgsEditorWidgetWrapper* eww = qobject_cast( ww ); + if ( eww ) + { + if ( ! eww->isValidConstraint() ) + { + invalidFields.append( eww->field().name() ); + valid = false; // continue to get all invalif fields + } + } + } + + return valid; +} + void QgsAttributeForm::onAttributeAdded( int idx ) { mPreventFeatureRefresh = false; @@ -749,11 +882,9 @@ void QgsAttributeForm::onUpdatedFields() setFeature( mFeature ); } -void QgsAttributeForm::onConstraintStatusChanged( const QString& constraint, bool ok ) +void QgsAttributeForm::onConstraintStatusChanged( const QString& constraint, + const QString& err, bool ok ) { - Q_UNUSED( constraint ) - - QgsEditorWidgetWrapper* eww = qobject_cast( sender() ); Q_ASSERT( eww ); @@ -761,6 +892,9 @@ void QgsAttributeForm::onConstraintStatusChanged( const QString& constraint, boo if ( buddy ) { + QString tooltip = "Expression: " + constraint + "\n" + "Constraint: " + err; + buddy->setToolTip( tooltip ); + if ( !buddy->property( "originalText" ).isValid() ) buddy->setProperty( "originalText", buddy->text() ); @@ -779,6 +913,38 @@ void QgsAttributeForm::onConstraintStatusChanged( const QString& constraint, boo } } +void QgsAttributeForm::constraintDependencies( QgsEditorWidgetWrapper *w, + QList &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( 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()->constraint( eww->fieldIdx() ); + + Q_FOREACH ( const QString& colName, expr.referencedColumns() ) + { + if ( name == colName ) + { + wDeps.append( eww ); + break; + } + } + } + } + } +} + void QgsAttributeForm::preventFeatureRefresh() { mPreventFeatureRefresh = true; @@ -817,6 +983,18 @@ void QgsAttributeForm::synchronizeEnabledState() ww->setEnabled( isEditable && fieldEditable ); } + // push a message and disable the OK button if constraints are invalid + clearInvalidConstraintsMessage(); + + QStringList invalidFields; + bool validConstraint = currentFormValidConstraints( invalidFields ); + + if ( ! validConstraint ) + displayInvalidConstraintMessage( invalidFields ); + + isEditable = isEditable & validConstraint; + + // change ok button status QPushButton* okButton = mButtonBox->button( QDialogButtonBox::Ok ); if ( okButton ) okButton->setEnabled( isEditable ); @@ -980,7 +1158,7 @@ void QgsAttributeForm::init() w = new QLabel( QString( "

Failed to create widget with type '%1'

" ).arg( widgetType ) ); } - l->setBuddy( w ); + l->setBuddy( eww->widget() ); if ( w ) w->setObjectName( field.name() ); @@ -1440,12 +1618,11 @@ void QgsAttributeForm::afterWidgetInit() } connect( eww, SIGNAL( valueChanged( const QVariant& ) ), this, SLOT( onAttributeChanged( const QVariant& ) ) ); - connect( eww, SIGNAL( constraintStatusChanged( QString, bool ) ), this, SLOT( onConstraintStatusChanged( QString, bool ) ) ); + connect( eww, SIGNAL( constraintStatusChanged( QString, QString, bool ) ), this, SLOT( onConstraintStatusChanged( QString, QString, bool ) ) ); } } // Update buddy widget list - mBuddyMap.clear(); QList labels = findChildren(); diff --git a/src/gui/qgsattributeform.h b/src/gui/qgsattributeform.h index a49c6546a28a..36803dcef04d 100644 --- a/src/gui/qgsattributeform.h +++ b/src/gui/qgsattributeform.h @@ -234,7 +234,7 @@ class GUI_EXPORT QgsAttributeForm : public QWidget void onAttributeAdded( int idx ); void onAttributeDeleted( int idx ); void onUpdatedFields(); - void onConstraintStatusChanged( const QString& constraint, bool ok ); + void onConstraintStatusChanged( const QString& constraint, const QString& err, bool ok ); void preventFeatureRefresh(); void synchronizeEnabledState(); @@ -298,6 +298,18 @@ 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 ); + void constraintDependencies( QgsEditorWidgetWrapper *w, QList &wDeps ); + void clearInvalidConstraintsMessage(); + void displayInvalidConstraintMessage( const QStringList &invalidFields ); + void displayNullFieldsMessage(); + QgsMessageBarItem *mInvalidConstraintMessageBarItem; + QgsMessageBarItem *mFieldNotInitializedMessageBarItem; + QgsVectorLayer* mLayer; QgsFeature mFeature; QgsMessageBar* mMessageBar; @@ -336,6 +348,7 @@ class GUI_EXPORT QgsAttributeForm : public QWidget QMap mBuddyMap; friend class TestQgsDualView; + friend class TestQgsAttributeForm; }; #endif // QGSATTRIBUTEFORM_H diff --git a/src/ui/qgsattributetypeedit.ui b/src/ui/qgsattributetypeedit.ui index adb2cb02bd8b..e488e8b48549 100644 --- a/src/ui/qgsattributetypeedit.ui +++ b/src/ui/qgsattributetypeedit.ui @@ -14,9 +14,6 @@ Edit Widget Properties - - - @@ -37,10 +34,10 @@ - + - + Qt::Horizontal @@ -50,6 +47,9 @@ + + + @@ -57,8 +57,32 @@ + + + + 0 + + + + + Constraint + + + + + + + + + + + QgsFieldExpressionWidget + QWidget +
qgsfieldexpressionwidget.h
+
+
selectionListWidget isFieldEditableCheckBox diff --git a/tests/src/gui/CMakeLists.txt b/tests/src/gui/CMakeLists.txt index 9b38411ce348..1f15140fa621 100644 --- a/tests/src/gui/CMakeLists.txt +++ b/tests/src/gui/CMakeLists.txt @@ -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) diff --git a/tests/src/gui/testqgsattributeform.cpp b/tests/src/gui/testqgsattributeform.cpp new file mode 100644 index 000000000000..07e146696892 --- /dev/null +++ b/tests/src/gui/testqgsattributeform.cpp @@ -0,0 +1,245 @@ +/*************************************************************************** + testqgsdualview.cpp + -------------------------------------- + Date : 13 05 2016 + Copyright : (C) 2016 Paul Blottiere + Email : paul.blottiere@oslandia.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 +#include + +#include +#include "qgsattributeform.h" +#include +#include +#include "qgsvectordataprovider.h" +#include + +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*"; + QString invalidLabel = "col0*"; + + // set constraint + layer->editFormConfig()->setConstraint( 0, "" ); + + // get wrapper + QgsEditorWidgetWrapper *ww; + ww = qobject_cast( 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()->setConstraint( 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()->setConstraint( 0, "" ); + layer->editFormConfig()->setConstraint( 1, "" ); + layer->editFormConfig()->setConstraint( 2, "" ); + layer->editFormConfig()->setConstraint( 3, "" ); + + // build a form for this feature + QgsAttributeForm form( layer ); + form.setFeature( ft ); + + // testing stuff + QSignalSpy spy( &form, SIGNAL( attributeChanged( QString, QVariant ) ) ); + QString val = "*"; + QString inv = "*"; + + // get wrappers for each widget + QgsEditorWidgetWrapper *ww0, *ww1, *ww2, *ww3; + ww0 = qobject_cast( form.mWidgets[0] ); + ww1 = qobject_cast( form.mWidgets[1] ); + ww2 = qobject_cast( form.mWidgets[2] ); + ww3 = qobject_cast( 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()->setConstraint( 0, "col0 < (col1 * col2)" ); + layer->editFormConfig()->setConstraint( 1, "" ); + layer->editFormConfig()->setConstraint( 2, "" ); + layer->editFormConfig()->setConstraint( 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( 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()->setConstraint( 0, "" ); + + // 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()->setConstraint( 0, "col0 = 0" ); + ww->setValue( 1 ); + QCOMPARE( okButton->isEnabled(), false ); + + // valid constraint and editable layer : OK button enabled + layer->editFormConfig()->setConstraint( 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" From aba02f11e04c8dcd82e0a141402f2e9e84da376a Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Thu, 19 May 2016 10:01:42 +0200 Subject: [PATCH 03/15] rename constraint to expression for method's name --- python/core/qgseditformconfig.sip | 4 +-- src/app/qgsattributetypedialog.cpp | 4 +-- src/app/qgsattributetypedialog.h | 4 +-- src/app/qgsfieldsproperties.cpp | 8 +++--- src/core/qgseditformconfig.cpp | 4 +-- src/core/qgseditformconfig.h | 4 +-- .../core/qgseditorwidgetregistry.cpp | 4 +-- .../core/qgseditorwidgetwrapper.cpp | 2 +- src/gui/qgsattributeform.cpp | 2 +- tests/src/gui/testqgsattributeform.cpp | 26 +++++++++---------- 10 files changed, 31 insertions(+), 31 deletions(-) diff --git a/python/core/qgseditformconfig.sip b/python/core/qgseditformconfig.sip index f776fd62d926..ee8e13674db2 100644 --- a/python/core/qgseditformconfig.sip +++ b/python/core/qgseditformconfig.sip @@ -482,7 +482,7 @@ class QgsEditFormConfig : QObject * @return the expression * @note added in QGIS 2.16 */ - QString constraint( int idx ) const; + QString expression( int idx ) const; /** * Set the constraint expression for a specific field @@ -490,7 +490,7 @@ class QgsEditFormConfig : QObject * @param str the constraint expression * @note added in QGIS 2.16 */ - void setConstraint( int idx, const QString& str ); + void setExpression( int idx, const QString& str ); /** * Returns if the field at fieldidx should be treated as NOT NULL value diff --git a/src/app/qgsattributetypedialog.cpp b/src/app/qgsattributetypedialog.cpp index 2d7b09d608c2..8b8cd2d13708 100644 --- a/src/app/qgsattributetypedialog.cpp +++ b/src/app/qgsattributetypedialog.cpp @@ -185,12 +185,12 @@ bool QgsAttributeTypeDialog::notNull() const return notNullCheckBox->isChecked(); } -void QgsAttributeTypeDialog::setConstraint( const QString &str ) +void QgsAttributeTypeDialog::setExpression( const QString &str ) { constraintExpression->setField( str ); } -QString QgsAttributeTypeDialog::constraint() const +QString QgsAttributeTypeDialog::expression() const { return constraintExpression->asExpression(); } diff --git a/src/app/qgsattributetypedialog.h b/src/app/qgsattributetypedialog.h index b7b3b43ef3a2..53c5e88bf1a0 100644 --- a/src/app/qgsattributetypedialog.h +++ b/src/app/qgsattributetypedialog.h @@ -98,13 +98,13 @@ class APP_EXPORT QgsAttributeTypeDialog: public QDialog, private Ui::QgsAttribut * Getter for the constraint expression * @note added in QGIS 2.16 */ - QString constraint() const; + QString expression() const; /** * Setter for the constraint expression * @note added in QGIS 2.16 */ - void setConstraint( const QString &str ); + void setExpression( const QString &str ); private slots: /** diff --git a/src/app/qgsfieldsproperties.cpp b/src/app/qgsfieldsproperties.cpp index 92015291070c..0b026992ea8d 100644 --- a/src/app/qgsfieldsproperties.cpp +++ b/src/app/qgsfieldsproperties.cpp @@ -528,7 +528,7 @@ void QgsFieldsProperties::attributeTypeDialog() attributeTypeDialog.setFieldEditable( cfg.mEditable ); attributeTypeDialog.setLabelOnTop( cfg.mLabelOnTop ); attributeTypeDialog.setNotNull( cfg.mNotNull ); - attributeTypeDialog.setConstraint( cfg.mConstraint ); + attributeTypeDialog.setExpression( cfg.mConstraint ); attributeTypeDialog.setWidgetV2Config( cfg.mEditorWidgetV2Config ); attributeTypeDialog.setWidgetV2Type( cfg.mEditorWidgetV2Type ); @@ -539,7 +539,7 @@ void QgsFieldsProperties::attributeTypeDialog() cfg.mEditable = attributeTypeDialog.fieldEditable(); cfg.mLabelOnTop = attributeTypeDialog.labelOnTop(); cfg.mNotNull = attributeTypeDialog.notNull(); - cfg.mConstraint = attributeTypeDialog.constraint(); + cfg.mConstraint = attributeTypeDialog.expression(); cfg.mEditorWidgetV2Type = attributeTypeDialog.editorWidgetV2Type(); cfg.mEditorWidgetV2Config = attributeTypeDialog.editorWidgetV2Config(); @@ -913,7 +913,7 @@ void QgsFieldsProperties::apply() mLayer->editFormConfig()->setReadOnly( i, !cfg.mEditable ); mLayer->editFormConfig()->setLabelOnTop( i, cfg.mLabelOnTop ); mLayer->editFormConfig()->setNotNull( i, cfg.mNotNull ); - mLayer->editFormConfig()->setConstraint( i, cfg.mConstraint ); + mLayer->editFormConfig()->setExpression( i, cfg.mConstraint ); mLayer->editFormConfig()->setWidgetType( idx, cfg.mEditorWidgetV2Type ); mLayer->editFormConfig()->setWidgetConfig( idx, cfg.mEditorWidgetV2Config ); @@ -993,7 +993,7 @@ QgsFieldsProperties::FieldConfig::FieldConfig( QgsVectorLayer* layer, int idx ) && layer->fields().fieldOrigin( idx ) != QgsFields::OriginExpression; mLabelOnTop = layer->editFormConfig()->labelOnTop( idx ); mNotNull = layer->editFormConfig()->notNull( idx ); - mConstraint = layer->editFormConfig()->constraint( idx ); + mConstraint = layer->editFormConfig()->expression( idx ); mEditorWidgetV2Type = layer->editFormConfig()->widgetType( idx ); mEditorWidgetV2Config = layer->editFormConfig()->widgetConfig( idx ); diff --git a/src/core/qgseditformconfig.cpp b/src/core/qgseditformconfig.cpp index 1311624d1b8b..8e153e22582a 100644 --- a/src/core/qgseditformconfig.cpp +++ b/src/core/qgseditformconfig.cpp @@ -119,7 +119,7 @@ bool QgsEditFormConfig::labelOnTop( int idx ) const return false; } -QString QgsEditFormConfig::constraint( int idx ) const +QString QgsEditFormConfig::expression( int idx ) const { QString expr = ""; @@ -129,7 +129,7 @@ QString QgsEditFormConfig::constraint( int idx ) const return expr; } -void QgsEditFormConfig::setConstraint( int idx, const QString& str ) +void QgsEditFormConfig::setExpression( int idx, const QString& str ) { if ( idx >= 0 && idx < mFields.count() ) mConstraints[ mFields.at( idx ).name()] = str; diff --git a/src/core/qgseditformconfig.h b/src/core/qgseditformconfig.h index 204600fc7620..0371314fc032 100644 --- a/src/core/qgseditformconfig.h +++ b/src/core/qgseditformconfig.h @@ -518,7 +518,7 @@ class CORE_EXPORT QgsEditFormConfig : public QObject * @return the expression * @note added in QGIS 2.16 */ - QString constraint( int idx ) const; + QString expression( int idx ) const; /** * Set the constraint expression for a specific field @@ -526,7 +526,7 @@ class CORE_EXPORT QgsEditFormConfig : public QObject * @param str the constraint expression * @note added in QGIS 2.16 */ - void setConstraint( int idx, const QString& str ); + void setExpression( int idx, const QString& str ); /** * Returns if the field at fieldidx should be treated as NOT NULL value diff --git a/src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp b/src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp index d6d9eef4368d..769294dae1d2 100644 --- a/src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp +++ b/src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp @@ -252,7 +252,7 @@ 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()->setConstraint( idx, ewv2CfgElem.attribute( "constraint", "" ) ); + vectorLayer->editFormConfig()->setExpression( idx, ewv2CfgElem.attribute( "constraint", "" ) ); vectorLayer->editFormConfig()->setWidgetConfig( idx, cfg ); } @@ -311,7 +311,7 @@ void QgsEditorWidgetRegistry::writeMapLayer( QgsMapLayer* mapLayer, QDomElement& 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()->constraint( idx ) ); + ewv2CfgElem.setAttribute( "constraint", vectorLayer->editFormConfig()->expression( idx ) ); mWidgetFactories[widgetType]->writeConfig( vectorLayer->editFormConfig()->widgetConfig( idx ), ewv2CfgElem, doc, vectorLayer, idx ); diff --git a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp index ef9959ad7662..c076bf682232 100644 --- a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp +++ b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp @@ -107,7 +107,7 @@ void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft ) { bool toEmit( false ); QString errStr( "predicate is True" ); - QString expression = layer()->editFormConfig()->constraint( mFieldIdx ); + QString expression = layer()->editFormConfig()->expression( mFieldIdx ); QVariant value = ft.attribute( mFieldIdx ); if ( ! expression.isEmpty() ) diff --git a/src/gui/qgsattributeform.cpp b/src/gui/qgsattributeform.cpp index 81d38ed6c08f..62f73de24307 100644 --- a/src/gui/qgsattributeform.cpp +++ b/src/gui/qgsattributeform.cpp @@ -930,7 +930,7 @@ void QgsAttributeForm::constraintDependencies( QgsEditorWidgetWrapper *w, if ( name != ewwName ) { // get expression and referencedColumns - QgsExpression expr = eww->layer()->editFormConfig()->constraint( eww->fieldIdx() ); + QgsExpression expr = eww->layer()->editFormConfig()->expression( eww->fieldIdx() ); Q_FOREACH ( const QString& colName, expr.referencedColumns() ) { diff --git a/tests/src/gui/testqgsattributeform.cpp b/tests/src/gui/testqgsattributeform.cpp index 07e146696892..4451bed428fd 100644 --- a/tests/src/gui/testqgsattributeform.cpp +++ b/tests/src/gui/testqgsattributeform.cpp @@ -81,7 +81,7 @@ void TestQgsAttributeForm::testFieldConstraint() QString invalidLabel = "col0*"; // set constraint - layer->editFormConfig()->setConstraint( 0, "" ); + layer->editFormConfig()->setExpression( 0, "" ); // get wrapper QgsEditorWidgetWrapper *ww; @@ -92,7 +92,7 @@ void TestQgsAttributeForm::testFieldConstraint() QCOMPARE( label->text(), QString( "col0" ) ); // set a not null constraint - layer->editFormConfig()->setConstraint( 0, "col0 is not null" ); + layer->editFormConfig()->setExpression( 0, "col0 is not null" ); // set value to 1 ww->setValue( 1 ); @@ -126,10 +126,10 @@ void TestQgsAttributeForm::testFieldMultiConstraints() ft.setAttribute( "col3", 3 ); // set constraints for each field - layer->editFormConfig()->setConstraint( 0, "" ); - layer->editFormConfig()->setConstraint( 1, "" ); - layer->editFormConfig()->setConstraint( 2, "" ); - layer->editFormConfig()->setConstraint( 3, "" ); + layer->editFormConfig()->setExpression( 0, "" ); + layer->editFormConfig()->setExpression( 1, "" ); + layer->editFormConfig()->setExpression( 2, "" ); + layer->editFormConfig()->setExpression( 3, "" ); // build a form for this feature QgsAttributeForm form( layer ); @@ -160,10 +160,10 @@ void TestQgsAttributeForm::testFieldMultiConstraints() QCOMPARE( label3->text(), QString( "col3" ) ); // update constraint - layer->editFormConfig()->setConstraint( 0, "col0 < (col1 * col2)" ); - layer->editFormConfig()->setConstraint( 1, "" ); - layer->editFormConfig()->setConstraint( 2, "" ); - layer->editFormConfig()->setConstraint( 3, "col0 = 2" ); + layer->editFormConfig()->setExpression( 0, "col0 < (col1 * col2)" ); + layer->editFormConfig()->setExpression( 1, "" ); + layer->editFormConfig()->setExpression( 2, "" ); + layer->editFormConfig()->setExpression( 3, "col0 = 2" ); // change value ww0->setValue( 2 ); // update col0 @@ -212,7 +212,7 @@ void TestQgsAttributeForm::testOKButtonStatus() QSignalSpy spy3( layer, SIGNAL( editingStopped() ) ); // set constraint - layer->editFormConfig()->setConstraint( 0, "" ); + layer->editFormConfig()->setExpression( 0, "" ); // no constraint but layer not editable : OK button disabled QCOMPARE( layer->isEditable(), false ); @@ -225,12 +225,12 @@ void TestQgsAttributeForm::testOKButtonStatus() QCOMPARE( okButton->isEnabled(), true ); // invalid constraint and editable layer : OK button disabled - layer->editFormConfig()->setConstraint( 0, "col0 = 0" ); + layer->editFormConfig()->setExpression( 0, "col0 = 0" ); ww->setValue( 1 ); QCOMPARE( okButton->isEnabled(), false ); // valid constraint and editable layer : OK button enabled - layer->editFormConfig()->setConstraint( 0, "col0 = 2" ); + layer->editFormConfig()->setExpression( 0, "col0 = 2" ); ww->setValue( 2 ); QCOMPARE( okButton->isEnabled(), true ); From 9ddb441855fa94b8dbca0d949b9d307a5f19fa0d Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Thu, 19 May 2016 10:03:19 +0200 Subject: [PATCH 04/15] fix test doc --- tests/src/gui/testqgsattributeform.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/src/gui/testqgsattributeform.cpp b/tests/src/gui/testqgsattributeform.cpp index 4451bed428fd..f6ac254bf43b 100644 --- a/tests/src/gui/testqgsattributeform.cpp +++ b/tests/src/gui/testqgsattributeform.cpp @@ -1,9 +1,9 @@ /*************************************************************************** - testqgsdualview.cpp + testqgsattributeform.cpp -------------------------------------- Date : 13 05 2016 Copyright : (C) 2016 Paul Blottiere - Email : paul.blottiere@oslandia.com + Email : paul dot blottiere at oslandia dot com *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * From a636319af6a6e41bef8511daa60f252a483f5bf2 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Thu, 19 May 2016 10:04:48 +0200 Subject: [PATCH 05/15] fix sip --- python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip b/python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip index 41634a01bac9..d50b593073f4 100644 --- a/python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip +++ b/python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip @@ -112,8 +112,10 @@ class QgsEditorWidgetWrapper : QgsWidgetWrapper void valueChanged( const QVariant& value ); /** + * Emit this signal when the constraint status changed. * @brief constraintStatusChanged - * @param constraint + * @param constraint represented as a string + * @param err the error represented as a string. Empty if none. * @param status */ void constraintStatusChanged( const QString& constraint, const QString& err, bool status ); From 7d71c973abe6fed330f18fa613df898501a5e648 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Thu, 19 May 2016 10:06:55 +0200 Subject: [PATCH 06/15] fix parameter's name camelcase --- src/app/qgsattributetypedialog.cpp | 4 ++-- src/app/qgsattributetypedialog.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/qgsattributetypedialog.cpp b/src/app/qgsattributetypedialog.cpp index 8b8cd2d13708..20e5779642af 100644 --- a/src/app/qgsattributetypedialog.cpp +++ b/src/app/qgsattributetypedialog.cpp @@ -170,9 +170,9 @@ bool QgsAttributeTypeDialog::fieldEditable() const return isFieldEditableCheckBox->isChecked(); } -void QgsAttributeTypeDialog::setNotNull( bool notnull ) +void QgsAttributeTypeDialog::setNotNull( bool notNull ) { - notNullCheckBox->setChecked( notnull ); + notNullCheckBox->setChecked( notNull ); } bool QgsAttributeTypeDialog::labelOnTop() const diff --git a/src/app/qgsattributetypedialog.h b/src/app/qgsattributetypedialog.h index 53c5e88bf1a0..b380a5899c95 100644 --- a/src/app/qgsattributetypedialog.h +++ b/src/app/qgsattributetypedialog.h @@ -87,7 +87,7 @@ class APP_EXPORT QgsAttributeTypeDialog: public QDialog, private Ui::QgsAttribut /** * Getter for checkbox for not null */ - void setNotNull( bool notnull ); + void setNotNull( bool notNull ); /** * Getter for checkbox for not null From ea097becae1c0cedceab2596e61b7bfbbd839f92 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Thu, 19 May 2016 10:18:39 +0200 Subject: [PATCH 07/15] replace "" by QString() --- src/core/qgseditformconfig.cpp | 4 ++-- .../core/qgseditorwidgetregistry.cpp | 2 +- .../core/qgseditorwidgetwrapper.cpp | 2 +- .../qgsexternalresourcewidgetwrapper.cpp | 4 ++-- .../editorwidgets/qgsfilenamewidgetwrapper.cpp | 2 +- src/gui/editorwidgets/qgsphotowidgetwrapper.cpp | 2 +- .../qgsrelationreferencewidgetwrapper.cpp | 4 ++-- .../editorwidgets/qgswebviewwidgetwrapper.cpp | 4 ++-- tests/src/gui/testqgsattributeform.cpp | 16 ++++++++-------- 9 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/core/qgseditformconfig.cpp b/src/core/qgseditformconfig.cpp index 8e153e22582a..b6404ab8fa29 100644 --- a/src/core/qgseditformconfig.cpp +++ b/src/core/qgseditformconfig.cpp @@ -121,10 +121,10 @@ bool QgsEditFormConfig::labelOnTop( int idx ) const QString QgsEditFormConfig::expression( int idx ) const { - QString expr = ""; + QString expr; if ( idx >= 0 && idx < mFields.count() ) - expr = mConstraints.value( mFields.at( idx ).name(), "" ); + expr = mConstraints.value( mFields.at( idx ).name(), QString() ); return expr; } diff --git a/src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp b/src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp index 769294dae1d2..3c16faf8be52 100644 --- a/src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp +++ b/src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp @@ -252,7 +252,7 @@ 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", "" ) ); + vectorLayer->editFormConfig()->setExpression( idx, ewv2CfgElem.attribute( "constraint", QString() ) ); vectorLayer->editFormConfig()->setWidgetConfig( idx, cfg ); } diff --git a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp index c076bf682232..809f70ba47c6 100644 --- a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp +++ b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp @@ -98,7 +98,7 @@ void QgsEditorWidgetWrapper::valueChanged() void QgsEditorWidgetWrapper::updateConstraintWidgetStatus() { if ( mValidConstraint ) - widget()->setStyleSheet( "" ); + widget()->setStyleSheet( QString() ); else widget()->setStyleSheet( "background-color: #dd7777;" ); } diff --git a/src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.cpp b/src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.cpp index 2c5c9f5b5c4e..722e849d26e2 100644 --- a/src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.cpp +++ b/src/gui/editorwidgets/qgsexternalresourcewidgetwrapper.cpp @@ -193,8 +193,8 @@ void QgsExternalResourceWidgetWrapper::updateConstraintWidgetStatus() if ( mLineEdit ) { if ( mValidConstraint ) - mLineEdit->setStyleSheet( "" ); + mLineEdit->setStyleSheet( QString() ); else mLineEdit->setStyleSheet( "QgsFilterLineEdit { background-color: #dd7777; }" ); } -} \ No newline at end of file +} diff --git a/src/gui/editorwidgets/qgsfilenamewidgetwrapper.cpp b/src/gui/editorwidgets/qgsfilenamewidgetwrapper.cpp index bb103db72b96..b636a34916ac 100644 --- a/src/gui/editorwidgets/qgsfilenamewidgetwrapper.cpp +++ b/src/gui/editorwidgets/qgsfilenamewidgetwrapper.cpp @@ -157,7 +157,7 @@ void QgsFileNameWidgetWrapper::updateConstraintWidgetStatus() if ( mLineEdit ) { if ( mValidConstraint ) - mLineEdit->setStyleSheet( "" ); + mLineEdit->setStyleSheet( QString() ); else { mLineEdit->setStyleSheet( "QgsFilterLineEdit { background-color: #dd7777; }" ); diff --git a/src/gui/editorwidgets/qgsphotowidgetwrapper.cpp b/src/gui/editorwidgets/qgsphotowidgetwrapper.cpp index f48c6c802db5..0955be5a6845 100644 --- a/src/gui/editorwidgets/qgsphotowidgetwrapper.cpp +++ b/src/gui/editorwidgets/qgsphotowidgetwrapper.cpp @@ -272,7 +272,7 @@ void QgsPhotoWidgetWrapper::updateConstraintWidgetStatus() if ( mLineEdit ) { if ( mValidConstraint ) - mLineEdit->setStyleSheet( "" ); + mLineEdit->setStyleSheet( QString() ); else { mLineEdit->setStyleSheet( "QgsFilterLineEdit { background-color: #dd7777; }" ); diff --git a/src/gui/editorwidgets/qgsrelationreferencewidgetwrapper.cpp b/src/gui/editorwidgets/qgsrelationreferencewidgetwrapper.cpp index 150c81f1de2b..f0e26bc77d96 100644 --- a/src/gui/editorwidgets/qgsrelationreferencewidgetwrapper.cpp +++ b/src/gui/editorwidgets/qgsrelationreferencewidgetwrapper.cpp @@ -145,8 +145,8 @@ void QgsRelationReferenceWidgetWrapper::updateConstraintWidgetStatus() if ( mWidget ) { if ( mValidConstraint ) - mWidget->setStyleSheet( "" ); + mWidget->setStyleSheet( QString() ); else mWidget->setStyleSheet( ".QComboBox { background-color: #dd7777; }" ); } -} \ No newline at end of file +} diff --git a/src/gui/editorwidgets/qgswebviewwidgetwrapper.cpp b/src/gui/editorwidgets/qgswebviewwidgetwrapper.cpp index 5fa44e72a458..b1955b69f70d 100644 --- a/src/gui/editorwidgets/qgswebviewwidgetwrapper.cpp +++ b/src/gui/editorwidgets/qgswebviewwidgetwrapper.cpp @@ -194,10 +194,10 @@ void QgsWebViewWidgetWrapper::updateConstraintWidgetStatus() if ( mLineEdit ) { if ( mValidConstraint ) - mLineEdit->setStyleSheet( "" ); + mLineEdit->setStyleSheet( QString() ); else { mLineEdit->setStyleSheet( "QgsFilterLineEdit { background-color: #dd7777; }" ); } } -} \ No newline at end of file +} diff --git a/tests/src/gui/testqgsattributeform.cpp b/tests/src/gui/testqgsattributeform.cpp index f6ac254bf43b..ca611b84b3bc 100644 --- a/tests/src/gui/testqgsattributeform.cpp +++ b/tests/src/gui/testqgsattributeform.cpp @@ -81,7 +81,7 @@ void TestQgsAttributeForm::testFieldConstraint() QString invalidLabel = "col0*"; // set constraint - layer->editFormConfig()->setExpression( 0, "" ); + layer->editFormConfig()->setExpression( 0, QString() ); // get wrapper QgsEditorWidgetWrapper *ww; @@ -126,10 +126,10 @@ void TestQgsAttributeForm::testFieldMultiConstraints() ft.setAttribute( "col3", 3 ); // set constraints for each field - layer->editFormConfig()->setExpression( 0, "" ); - layer->editFormConfig()->setExpression( 1, "" ); - layer->editFormConfig()->setExpression( 2, "" ); - layer->editFormConfig()->setExpression( 3, "" ); + 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 ); @@ -161,8 +161,8 @@ void TestQgsAttributeForm::testFieldMultiConstraints() // update constraint layer->editFormConfig()->setExpression( 0, "col0 < (col1 * col2)" ); - layer->editFormConfig()->setExpression( 1, "" ); - layer->editFormConfig()->setExpression( 2, "" ); + layer->editFormConfig()->setExpression( 1, QString() ); + layer->editFormConfig()->setExpression( 2, QString() ); layer->editFormConfig()->setExpression( 3, "col0 = 2" ); // change value @@ -212,7 +212,7 @@ void TestQgsAttributeForm::testOKButtonStatus() QSignalSpy spy3( layer, SIGNAL( editingStopped() ) ); // set constraint - layer->editFormConfig()->setExpression( 0, "" ); + layer->editFormConfig()->setExpression( 0, QString() ); // no constraint but layer not editable : OK button disabled QCOMPARE( layer->isEditable(), false ); From 8d25a067a9db76d804a8b1ebb96c8b0fe31268bd Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Thu, 19 May 2016 10:19:54 +0200 Subject: [PATCH 08/15] rename feature -> f to avoid an API break --- src/core/qgsvectorlayer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/qgsvectorlayer.h b/src/core/qgsvectorlayer.h index 3adb15401af7..45642c4772e6 100644 --- a/src/core/qgsvectorlayer.h +++ b/src/core/qgsvectorlayer.h @@ -979,7 +979,7 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer @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& feature, bool alsoUpdateExtent = true ); + bool addFeature( QgsFeature& f, bool alsoUpdateExtent = true ); /** Updates an existing feature. This method needs to query the datasource on every call. Consider using {@link changeAttributeValue()} or From 3947cb93f8c4e07a830e9b779510b297bb1dc32a Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Thu, 19 May 2016 10:36:08 +0200 Subject: [PATCH 09/15] translatable string --- src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp | 6 +++--- src/gui/qgsattributeform.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp index 809f70ba47c6..d4673742ea02 100644 --- a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp +++ b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp @@ -106,7 +106,7 @@ void QgsEditorWidgetWrapper::updateConstraintWidgetStatus() void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft ) { bool toEmit( false ); - QString errStr( "predicate is True" ); + QString errStr( tr( "predicate is True" ) ); QString expression = layer()->editFormConfig()->expression( mFieldIdx ); QVariant value = ft.attribute( mFieldIdx ); @@ -125,7 +125,7 @@ void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft ) else if ( expr.hasEvalError() ) errStr = expr.evalErrorString(); else if ( ! mValidConstraint ) - errStr = "predicate is False"; + errStr = tr( "predicate is False" ); toEmit = true; } @@ -145,7 +145,7 @@ void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft ) mValidConstraint = mValidConstraint && !value.isNull(); if ( value.isNull() ) - errStr = "predicate is False"; + errStr = tr( "predicate is False" ); toEmit = true; } diff --git a/src/gui/qgsattributeform.cpp b/src/gui/qgsattributeform.cpp index 62f73de24307..f68ec46f8cda 100644 --- a/src/gui/qgsattributeform.cpp +++ b/src/gui/qgsattributeform.cpp @@ -892,7 +892,7 @@ void QgsAttributeForm::onConstraintStatusChanged( const QString& constraint, if ( buddy ) { - QString tooltip = "Expression: " + constraint + "\n" + "Constraint: " + err; + QString tooltip = tr( "Expression: " ) + constraint + "\n" + tr( "Constraint: " ) + err; buddy->setToolTip( tooltip ); if ( !buddy->property( "originalText" ).isValid() ) From 6489b62b4a0a7946d7b2be793d75a1a123e76b4f Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Thu, 19 May 2016 10:38:24 +0200 Subject: [PATCH 10/15] add layer scope in context to evaluate the constraint expression --- src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp index d4673742ea02..c0e8372c07b6 100644 --- a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp +++ b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp @@ -114,6 +114,7 @@ void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft ) { QgsExpressionContext context = QgsExpressionContextUtils::createFeatureBasedContext( ft, *ft.fields() ); + context << QgsExpressionContextUtils::layerScope( layer() ); context.setFeature( ft ); QgsExpression expr( expression ); From bffe308aab101c61bde50895eb9777c3187ed029 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Fri, 20 May 2016 08:20:11 +0200 Subject: [PATCH 11/15] fix doc --- src/core/qgsvectorlayer.h | 2 +- src/gui/editorwidgets/core/qgseditorwidgetwrapper.h | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/core/qgsvectorlayer.h b/src/core/qgsvectorlayer.h index 45642c4772e6..3adb15401af7 100644 --- a/src/core/qgsvectorlayer.h +++ b/src/core/qgsvectorlayer.h @@ -979,7 +979,7 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer @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 diff --git a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h index 57e7274f1985..38b8a89a7601 100644 --- a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h +++ b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h @@ -223,6 +223,9 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper */ virtual void updateConstraintWidgetStatus(); + /** + * Boolean storing the current validity of the constraint for this widget. + */ bool mValidConstraint; private: From 8449e42ae0b94cc40fa68d08a2790023043a58c8 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Sat, 28 May 2016 10:52:38 +0200 Subject: [PATCH 12/15] add constraint description --- python/core/qgseditformconfig.sip | 16 +++++++++ .../core/qgseditorwidgetwrapper.sip | 1 + src/app/qgsattributetypedialog.cpp | 10 ++++++ src/app/qgsattributetypedialog.h | 16 ++++++++- src/app/qgsfieldsproperties.cpp | 5 +++ src/app/qgsfieldsproperties.h | 1 + src/core/qgseditformconfig.cpp | 16 +++++++++ src/core/qgseditformconfig.h | 29 +++++++++++---- .../core/qgseditorwidgetregistry.cpp | 2 ++ .../core/qgseditorwidgetwrapper.cpp | 9 ++++- .../core/qgseditorwidgetwrapper.h | 3 +- src/gui/qgsattributeform.cpp | 36 +++++++++++++------ src/gui/qgsattributeform.h | 9 ++--- src/ui/qgsattributetypeedit.ui | 17 +++++++++ 14 files changed, 146 insertions(+), 24 deletions(-) diff --git a/python/core/qgseditformconfig.sip b/python/core/qgseditformconfig.sip index ee8e13674db2..acc8f1a38fc3 100644 --- a/python/core/qgseditformconfig.sip +++ b/python/core/qgseditformconfig.sip @@ -492,6 +492,22 @@ class QgsEditFormConfig : QObject */ 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 */ diff --git a/python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip b/python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip index d50b593073f4..7515f6600b5a 100644 --- a/python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip +++ b/python/gui/editorwidgets/core/qgseditorwidgetwrapper.sip @@ -115,6 +115,7 @@ class QgsEditorWidgetWrapper : QgsWidgetWrapper * 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 */ diff --git a/src/app/qgsattributetypedialog.cpp b/src/app/qgsattributetypedialog.cpp index 20e5779642af..6791bd94bf07 100644 --- a/src/app/qgsattributetypedialog.cpp +++ b/src/app/qgsattributetypedialog.cpp @@ -180,6 +180,16 @@ 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(); diff --git a/src/app/qgsattributetypedialog.h b/src/app/qgsattributetypedialog.h index b380a5899c95..012ea3e318d5 100644 --- a/src/app/qgsattributetypedialog.h +++ b/src/app/qgsattributetypedialog.h @@ -85,7 +85,7 @@ class APP_EXPORT QgsAttributeTypeDialog: public QDialog, private Ui::QgsAttribut bool fieldEditable() const; /** - * Getter for checkbox for not null + * Setter for checkbox for not null */ void setNotNull( bool notNull ); @@ -94,6 +94,20 @@ class APP_EXPORT QgsAttributeTypeDialog: public QDialog, private Ui::QgsAttribut */ 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 diff --git a/src/app/qgsfieldsproperties.cpp b/src/app/qgsfieldsproperties.cpp index 0b026992ea8d..8a0aa3dddffe 100644 --- a/src/app/qgsfieldsproperties.cpp +++ b/src/app/qgsfieldsproperties.cpp @@ -529,6 +529,7 @@ void QgsFieldsProperties::attributeTypeDialog() attributeTypeDialog.setLabelOnTop( cfg.mLabelOnTop ); attributeTypeDialog.setNotNull( cfg.mNotNull ); attributeTypeDialog.setExpression( cfg.mConstraint ); + attributeTypeDialog.setExpressionDescription( cfg.mConstraintDescription ); attributeTypeDialog.setWidgetV2Config( cfg.mEditorWidgetV2Config ); attributeTypeDialog.setWidgetV2Type( cfg.mEditorWidgetV2Type ); @@ -539,6 +540,7 @@ 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(); @@ -913,6 +915,7 @@ 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 ); @@ -981,6 +984,7 @@ QgsFieldsProperties::FieldConfig::FieldConfig() , mEditableEnabled( true ) , mLabelOnTop( false ) , mNotNull( false ) + , mConstraintDescription( QString() ) , mButton( nullptr ) { } @@ -994,6 +998,7 @@ QgsFieldsProperties::FieldConfig::FieldConfig( QgsVectorLayer* layer, int idx ) 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 ); diff --git a/src/app/qgsfieldsproperties.h b/src/app/qgsfieldsproperties.h index 1d982c99111b..39d96eb6e825 100644 --- a/src/app/qgsfieldsproperties.h +++ b/src/app/qgsfieldsproperties.h @@ -94,6 +94,7 @@ class APP_EXPORT QgsFieldsProperties : public QWidget, private Ui_QgsFieldsPrope bool mLabelOnTop; bool mNotNull; QString mConstraint; + QString mConstraintDescription; QPushButton* mButton; QString mEditorWidgetV2Type; QMap mEditorWidgetV2Config; diff --git a/src/core/qgseditformconfig.cpp b/src/core/qgseditformconfig.cpp index b6404ab8fa29..17ee3bd7d712 100644 --- a/src/core/qgseditformconfig.cpp +++ b/src/core/qgseditformconfig.cpp @@ -135,6 +135,22 @@ void QgsEditFormConfig::setExpression( int idx, const QString& str ) 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() ) diff --git a/src/core/qgseditformconfig.h b/src/core/qgseditformconfig.h index 0371314fc032..4d0c7634fcf5 100644 --- a/src/core/qgseditformconfig.h +++ b/src/core/qgseditformconfig.h @@ -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 ); /** @@ -528,6 +528,22 @@ class CORE_EXPORT QgsEditFormConfig : public QObject */ 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 */ @@ -657,6 +673,7 @@ class CORE_EXPORT QgsEditFormConfig : public QObject QList< TabData > mTabs; QMap< QString, QString> mConstraints; + QMap< QString, QString> mConstraintsDescription; QMap< QString, bool> mFieldEditables; QMap< QString, bool> mLabelOnTop; QMap< QString, bool> mNotNull; diff --git a/src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp b/src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp index 3c16faf8be52..649f2d35bdcb 100644 --- a/src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp +++ b/src/gui/editorwidgets/core/qgseditorwidgetregistry.cpp @@ -253,6 +253,7 @@ void QgsEditorWidgetRegistry::readMapLayer( QgsMapLayer* mapLayer, const QDomEle 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 ); } @@ -312,6 +313,7 @@ void QgsEditorWidgetRegistry::writeMapLayer( QgsMapLayer* mapLayer, QDomElement& 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 ); diff --git a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp index c0e8372c07b6..7e4056b22839 100644 --- a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp +++ b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.cpp @@ -108,10 +108,13 @@ 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() ); @@ -139,9 +142,13 @@ void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft ) { 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(); @@ -154,7 +161,7 @@ void QgsEditorWidgetWrapper::updateConstraint( const QgsFeature &ft ) if ( toEmit ) { updateConstraintWidgetStatus(); - emit constraintStatusChanged( expression, errStr, mValidConstraint ); + emit constraintStatusChanged( expression, description, errStr, mValidConstraint ); } } diff --git a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h index 38b8a89a7601..d6ec48612e3f 100644 --- a/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h +++ b/src/gui/editorwidgets/core/qgseditorwidgetwrapper.h @@ -137,10 +137,11 @@ class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper * 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 ); + void constraintStatusChanged( const QString& constraint, const QString &desc, const QString& err, bool status ); public slots: /** diff --git a/src/gui/qgsattributeform.cpp b/src/gui/qgsattributeform.cpp index f68ec46f8cda..91057fda7940 100644 --- a/src/gui/qgsattributeform.cpp +++ b/src/gui/qgsattributeform.cpp @@ -795,17 +795,26 @@ void QgsAttributeForm::clearInvalidConstraintsMessage() } } -void QgsAttributeForm::displayInvalidConstraintMessage( const QStringList &f ) +void QgsAttributeForm::displayInvalidConstraintMessage( const QStringList &f, + const QStringList &d ) { clearInvalidConstraintsMessage(); + // show only the third first error + int max = 3; + int size = f.size() > max ? max : f.size(); + QString descriptions; + for ( int i = 0; i < size; i++ ) + descriptions += "
- " + f[i] + ": " + d[i]; + mInvalidConstraintMessageBarItem = - new QgsMessageBarItem( tr( "Invalid fields:" ), - f.join( ", " ), QgsMessageBar::WARNING ); + new QgsMessageBarItem( tr( "Invalid fields: " ), + descriptions, QgsMessageBar::WARNING ); mMessageBar->pushItem( mInvalidConstraintMessageBarItem ); } -bool QgsAttributeForm::currentFormValidConstraints( QStringList &invalidFields ) +bool QgsAttributeForm::currentFormValidConstraints( QStringList &invalidFields, + QStringList &descriptions ) { bool valid( true ); @@ -817,6 +826,10 @@ bool QgsAttributeForm::currentFormValidConstraints( QStringList &invalidFields ) 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 } } @@ -883,7 +896,7 @@ void QgsAttributeForm::onUpdatedFields() } void QgsAttributeForm::onConstraintStatusChanged( const QString& constraint, - const QString& err, bool ok ) + const QString &description, const QString& err, bool ok ) { QgsEditorWidgetWrapper* eww = qobject_cast( sender() ); Q_ASSERT( eww ); @@ -892,7 +905,8 @@ void QgsAttributeForm::onConstraintStatusChanged( const QString& constraint, if ( buddy ) { - QString tooltip = tr( "Expression: " ) + constraint + "\n" + tr( "Constraint: " ) + err; + QString tooltip = tr( "Description: " ) + description + "\n" + + tr( "Raw expression: " ) + constraint + "\n" + tr( "Constraint: " ) + err; buddy->setToolTip( tooltip ); if ( !buddy->property( "originalText" ).isValid() ) @@ -986,11 +1000,11 @@ void QgsAttributeForm::synchronizeEnabledState() // push a message and disable the OK button if constraints are invalid clearInvalidConstraintsMessage(); - QStringList invalidFields; - bool validConstraint = currentFormValidConstraints( invalidFields ); + QStringList invalidFields, descriptions; + bool validConstraint = currentFormValidConstraints( invalidFields, descriptions ); if ( ! validConstraint ) - displayInvalidConstraintMessage( invalidFields ); + displayInvalidConstraintMessage( invalidFields, descriptions ); isEditable = isEditable & validConstraint; @@ -1205,7 +1219,6 @@ void QgsAttributeForm::init() } mButtonBox->setVisible( buttonBoxVisible ); -<<<<<<< HEAD if ( !mSearchButtonBox ) { mSearchButtonBox = new QWidget(); @@ -1618,7 +1631,8 @@ void QgsAttributeForm::afterWidgetInit() } connect( eww, SIGNAL( valueChanged( const QVariant& ) ), this, SLOT( onAttributeChanged( const QVariant& ) ) ); - connect( eww, SIGNAL( constraintStatusChanged( QString, QString, bool ) ), this, SLOT( onConstraintStatusChanged( QString, QString, bool ) ) ); + connect( eww, SIGNAL( constraintStatusChanged( QString, QString, QString, bool ) ), + this, SLOT( onConstraintStatusChanged( QString, QString, QString, bool ) ) ); } } diff --git a/src/gui/qgsattributeform.h b/src/gui/qgsattributeform.h index 36803dcef04d..319daf716747 100644 --- a/src/gui/qgsattributeform.h +++ b/src/gui/qgsattributeform.h @@ -234,8 +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& err, bool ok ); - + void onConstraintStatusChanged( const QString& constraint, + const QString &description, const QString& err, bool ok ); void preventFeatureRefresh(); void synchronizeEnabledState(); void layerSelectionChanged(); @@ -302,10 +302,11 @@ class GUI_EXPORT QgsAttributeForm : public QWidget void updateAllConstaints(); void updateConstraints( QgsEditorWidgetWrapper *w ); bool currentFormFeature( QgsFeature &feature ); - bool currentFormValidConstraints( QStringList &invalidFields ); + bool currentFormValidConstraints( QStringList &invalidFields, QStringList &descriptions ); void constraintDependencies( QgsEditorWidgetWrapper *w, QList &wDeps ); void clearInvalidConstraintsMessage(); - void displayInvalidConstraintMessage( const QStringList &invalidFields ); + void displayInvalidConstraintMessage( const QStringList &invalidFields, + const QStringList &description ); void displayNullFieldsMessage(); QgsMessageBarItem *mInvalidConstraintMessageBarItem; QgsMessageBarItem *mFieldNotInitializedMessageBarItem; diff --git a/src/ui/qgsattributetypeedit.ui b/src/ui/qgsattributetypeedit.ui index e488e8b48549..e8175b7298dc 100644 --- a/src/ui/qgsattributetypeedit.ui +++ b/src/ui/qgsattributetypeedit.ui @@ -74,6 +74,23 @@ + + + + 0 + + + + + Constraint description + + + + + + + + From 36d7dc4ef01384a8ec7b11f03a51750ad0f7f7af Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Sun, 29 May 2016 15:59:08 +0200 Subject: [PATCH 13/15] remove null information message bar --- src/gui/qgsattributeform.cpp | 26 -------------------------- src/gui/qgsattributeform.h | 2 -- 2 files changed, 28 deletions(-) diff --git a/src/gui/qgsattributeform.cpp b/src/gui/qgsattributeform.cpp index 91057fda7940..31ba2f0e12c7 100644 --- a/src/gui/qgsattributeform.cpp +++ b/src/gui/qgsattributeform.cpp @@ -50,7 +50,6 @@ int QgsAttributeForm::sFormCounter = 0; QgsAttributeForm::QgsAttributeForm( QgsVectorLayer* vl, const QgsFeature &feature, const QgsAttributeEditorContext &context, QWidget* parent ) : QWidget( parent ) , mInvalidConstraintMessageBarItem( nullptr ) - , mFieldNotInitializedMessageBarItem( nullptr ) , mLayer( vl ) , mMessageBar( nullptr ) , mMultiEditUnsavedMessageBarItem( nullptr ) @@ -83,7 +82,6 @@ QgsAttributeForm::QgsAttributeForm( QgsVectorLayer* vl, const QgsFeature &featur connect( vl, SIGNAL( selectionChanged() ), this, SLOT( layerSelectionChanged() ) ); // constraints management - displayNullFieldsMessage(); updateAllConstaints(); } @@ -762,30 +760,6 @@ bool QgsAttributeForm::currentFormFeature( QgsFeature &feature ) return rc; } -void QgsAttributeForm::displayNullFieldsMessage() -{ - QStringList notInitializedFields; - Q_FOREACH ( QgsWidgetWrapper* ww, mWidgets ) - { - QgsEditorWidgetWrapper* eww = qobject_cast( ww ); - if ( eww ) - { - if ( mFeature.attribute( eww->fieldIdx() ).isNull() ) - notInitializedFields.append( eww->field().name() ); - } - } - - if ( ! notInitializedFields.isEmpty() ) - { - mFieldNotInitializedMessageBarItem = - new QgsMessageBarItem( tr( "Some fields are NULL: " ), - notInitializedFields.join( ", " ), - QgsMessageBar::INFO ); - mMessageBar->pushItem( mFieldNotInitializedMessageBarItem ); - } - -} - void QgsAttributeForm::clearInvalidConstraintsMessage() { if ( mInvalidConstraintMessageBarItem != nullptr ) diff --git a/src/gui/qgsattributeform.h b/src/gui/qgsattributeform.h index 319daf716747..b9655841ead8 100644 --- a/src/gui/qgsattributeform.h +++ b/src/gui/qgsattributeform.h @@ -307,9 +307,7 @@ class GUI_EXPORT QgsAttributeForm : public QWidget void clearInvalidConstraintsMessage(); void displayInvalidConstraintMessage( const QStringList &invalidFields, const QStringList &description ); - void displayNullFieldsMessage(); QgsMessageBarItem *mInvalidConstraintMessageBarItem; - QgsMessageBarItem *mFieldNotInitializedMessageBarItem; QgsVectorLayer* mLayer; QgsFeature mFeature; From ea950340dba882fcf175945d12561eab281ad579 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Sun, 29 May 2016 16:49:52 +0200 Subject: [PATCH 14/15] replace the message bar by a multiline qlabel --- src/gui/qgsattributeform.cpp | 28 ++++++++++++++++------------ src/gui/qgsattributeform.h | 2 +- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/gui/qgsattributeform.cpp b/src/gui/qgsattributeform.cpp index 31ba2f0e12c7..532f729993da 100644 --- a/src/gui/qgsattributeform.cpp +++ b/src/gui/qgsattributeform.cpp @@ -49,11 +49,11 @@ int QgsAttributeForm::sFormCounter = 0; QgsAttributeForm::QgsAttributeForm( QgsVectorLayer* vl, const QgsFeature &feature, const QgsAttributeEditorContext &context, QWidget* parent ) : QWidget( parent ) - , mInvalidConstraintMessageBarItem( nullptr ) , mLayer( vl ) , mMessageBar( nullptr ) , mMultiEditUnsavedMessageBarItem( nullptr ) , mMultiEditMessageBarItem( nullptr ) + , mInvalidConstraintMessage( nullptr ) , mContext( context ) , mButtonBox( nullptr ) , mSearchButtonBox( nullptr ) @@ -762,11 +762,8 @@ bool QgsAttributeForm::currentFormFeature( QgsFeature &feature ) void QgsAttributeForm::clearInvalidConstraintsMessage() { - if ( mInvalidConstraintMessageBarItem != nullptr ) - { - mMessageBar->popWidget( mInvalidConstraintMessageBarItem ); - mInvalidConstraintMessageBarItem = nullptr; - } + mInvalidConstraintMessage->clear(); + mInvalidConstraintMessage->setStyleSheet( "" ); } void QgsAttributeForm::displayInvalidConstraintMessage( const QStringList &f, @@ -774,17 +771,20 @@ void QgsAttributeForm::displayInvalidConstraintMessage( const QStringList &f, { clearInvalidConstraintsMessage(); - // show only the third first error + // 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 += "
- " + f[i] + ": " + d[i]; + descriptions += QString( "
  • %1: %2
  • " ).arg( f[i] ).arg( d[i] ); + + QString icPath = QgsApplication::iconPath( "/mIconWarn.png" ); - mInvalidConstraintMessageBarItem = - new QgsMessageBarItem( tr( "Invalid fields: " ), - descriptions, QgsMessageBar::WARNING ); - mMessageBar->pushItem( mInvalidConstraintMessageBarItem ); + QString title = QString( " %2:" ).arg( icPath ).arg( tr( "Invalid fields" ) ); + QString msg = QString( "%1
      %2
    " ).arg( title ).arg( descriptions ) ; + + mInvalidConstraintMessage->setText( msg ); + mInvalidConstraintMessage->setStyleSheet( "QLabel { background-color : #ffc800; }" ); } bool QgsAttributeForm::currentFormValidConstraints( QStringList &invalidFields, @@ -1025,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 diff --git a/src/gui/qgsattributeform.h b/src/gui/qgsattributeform.h index b9655841ead8..659e7ebf63db 100644 --- a/src/gui/qgsattributeform.h +++ b/src/gui/qgsattributeform.h @@ -307,13 +307,13 @@ class GUI_EXPORT QgsAttributeForm : public QWidget void clearInvalidConstraintsMessage(); void displayInvalidConstraintMessage( const QStringList &invalidFields, const QStringList &description ); - QgsMessageBarItem *mInvalidConstraintMessageBarItem; QgsVectorLayer* mLayer; QgsFeature mFeature; QgsMessageBar* mMessageBar; QgsMessageBarItem* mMultiEditUnsavedMessageBarItem; QgsMessageBarItem* mMultiEditMessageBarItem; + QLabel* mInvalidConstraintMessage; QList mWidgets; QgsAttributeEditorContext mContext; QDialogButtonBox* mButtonBox; From 8774ad21b808ad92620c06525cec522161f43429 Mon Sep 17 00:00:00 2001 From: Blottiere Paul Date: Tue, 31 May 2016 20:12:53 +0200 Subject: [PATCH 15/15] fix unit test --- src/gui/qgsattributeform.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gui/qgsattributeform.cpp b/src/gui/qgsattributeform.cpp index 532f729993da..c9b47804a76c 100644 --- a/src/gui/qgsattributeform.cpp +++ b/src/gui/qgsattributeform.cpp @@ -1144,13 +1144,14 @@ void QgsAttributeForm::init() w = formWidget; mFormEditorWidgets.insert( idx, formWidget ); formWidget->createSearchWidgetWrappers( widgetType, idx, widgetConfig, mContext ); + + l->setBuddy( eww->widget() ); } else { w = new QLabel( QString( "

    Failed to create widget with type '%1'

    " ).arg( widgetType ) ); } - l->setBuddy( eww->widget() ); if ( w ) w->setObjectName( field.name() );