From 1882cabea9aeec06d726e9b17759165ef3ad75ab Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Thu, 8 Sep 2016 12:19:23 +0200 Subject: [PATCH] [Feature] conditional visibility for tabs and groupboxes This adds a new configuration option to conditionally show or hide tabs and groupboxes in drag and drop designer forms. Configuration is done via a double click in the designer tree in the fields configuration interface. An expression can be entered to control the visibility. The expression will be re-evaluated everytime values in the form change and the tab or groupbox shown/hidden accordingly. --- src/app/qgsfieldsproperties.cpp | 36 +++++++++++++++-- src/app/qgsfieldsproperties.h | 9 ++++- src/core/qgsattributeeditorelement.cpp | 13 +++++++ src/core/qgsattributeeditorelement.h | 5 +++ src/core/qgseditformconfig.cpp | 13 +++++++ src/gui/qgsattributeform.cpp | 54 ++++++++++++++++++++++++-- src/gui/qgsattributeform.h | 32 +++++++++++++++ src/gui/qgsfieldexpressionwidget.cpp | 10 +++++ src/gui/qgsfieldexpressionwidget.h | 22 ++++++++++- 9 files changed, 186 insertions(+), 8 deletions(-) diff --git a/src/app/qgsfieldsproperties.cpp b/src/app/qgsfieldsproperties.cpp index 870945fe040..f8454b93079 100644 --- a/src/app/qgsfieldsproperties.cpp +++ b/src/app/qgsfieldsproperties.cpp @@ -28,6 +28,7 @@ #include "qgsrelationmanager.h" #include "qgsvectordataprovider.h" #include "qgsvectorlayer.h" +#include "qgsfieldexpressionwidget.h" #include #include @@ -72,7 +73,7 @@ QgsFieldsProperties::QgsFieldsProperties( QgsVectorLayer *layer, QWidget* parent // tab and group display mAddItemButton->setEnabled( false ); - mDesignerTree = new DesignerTree( mAttributesTreeFrame ); + mDesignerTree = new DesignerTree( mLayer, mAttributesTreeFrame ); mDesignerListLayout->addWidget( mDesignerTree ); mDesignerTree->setHeaderLabels( QStringList() << tr( "Label" ) ); @@ -154,7 +155,7 @@ void QgsFieldsProperties::onAttributeSelectionChanged() updateButtons(); } -QTreeWidgetItem *QgsFieldsProperties::loadAttributeEditorTreeItem( QgsAttributeEditorElement* const widgetDef, QTreeWidgetItem* parent ) +QTreeWidgetItem* QgsFieldsProperties::loadAttributeEditorTreeItem( QgsAttributeEditorElement* const widgetDef, QTreeWidgetItem* parent ) { QTreeWidgetItem* newWidget = nullptr; switch ( widgetDef->type() ) @@ -187,6 +188,7 @@ QTreeWidgetItem *QgsFieldsProperties::loadAttributeEditorTreeItem( QgsAttributeE itemData.setColumnCount( container->columnCount() ); itemData.setShowAsGroupBox( container->isGroupBox() ); + itemData.setVisibilityExpression( container->visibilityExpression() ); newWidget = mDesignerTree->addItem( parent, itemData ); Q_FOREACH ( QgsAttributeEditorElement* wdg, container->children() ) @@ -865,6 +867,7 @@ QgsAttributeEditorElement* QgsFieldsProperties::createAttributeEditorWidget( QTr QgsAttributeEditorContainer* container = new QgsAttributeEditorContainer( item->text( 0 ), parent ); container->setColumnCount( itemData.columnCount() ); container->setIsGroupBox( forceGroup ? true : itemData.showAsGroupBox() ); + container->setVisibilityExpression( itemData.visibilityExpression() ); for ( int t = 0; t < item->childCount(); t++ ) { @@ -1092,8 +1095,9 @@ QTreeWidgetItem* DesignerTree::addContainer( QTreeWidgetItem* parent, const QStr return newItem; } -DesignerTree::DesignerTree( QWidget* parent ) +DesignerTree::DesignerTree( QgsVectorLayer* layer, QWidget* parent ) : QTreeWidget( parent ) + , mLayer( layer ) { connect( this, SIGNAL( itemDoubleClicked( QTreeWidgetItem*, int ) ), this, SLOT( onItemDoubleClicked( QTreeWidgetItem*, int ) ) ); } @@ -1271,11 +1275,22 @@ void DesignerTree::onItemDoubleClicked( QTreeWidgetItem* item, int column ) QCheckBox* showAsGroupBox = nullptr; QLineEdit* title = new QLineEdit( itemData.name() ); QSpinBox* columnCount = new QSpinBox(); + QGroupBox* visibilityExpressionGroupBox = new QGroupBox( tr( "Control visibility by expression " ) ); + visibilityExpressionGroupBox->setCheckable( true ); + visibilityExpressionGroupBox->setChecked( itemData.visibilityExpression().enabled() ); + visibilityExpressionGroupBox->setLayout( new QGridLayout ); + QgsFieldExpressionWidget* visibilityExpressionWidget = new QgsFieldExpressionWidget; + visibilityExpressionWidget->setLayer( mLayer ); + visibilityExpressionWidget->setExpressionDialogTitle( tr( "Visibility expression" ) ); + visibilityExpressionWidget->setExpression( itemData.visibilityExpression()->expression() ); + visibilityExpressionGroupBox->layout()->addWidget( visibilityExpressionWidget ); + columnCount->setRange( 1, 5 ); columnCount->setValue( itemData.columnCount() ); layout->addRow( tr( "Title" ), title ); layout->addRow( tr( "Column count" ), columnCount ); + layout->addWidget( visibilityExpressionGroupBox ); if ( !item->parent() ) { @@ -1299,6 +1314,11 @@ void DesignerTree::onItemDoubleClicked( QTreeWidgetItem* item, int column ) itemData.setName( title->text() ); itemData.setShowLabel( showLabelCheckbox->isChecked() ); + QgsOptionalExpression visibilityExpression; + visibilityExpression.setData( QgsExpression( visibilityExpressionWidget->expression() ) ); + visibilityExpression.setEnabled( visibilityExpressionGroupBox->isChecked() ); + itemData.setVisibilityExpression( visibilityExpression ); + item->setData( 0, QgsFieldsProperties::DesignerTreeRole, itemData.asQVariant() ); item->setText( 0, title->text() ); } @@ -1369,3 +1389,13 @@ void QgsFieldsProperties::DesignerTreeItemData::setShowLabel( bool showLabel ) { mShowLabel = showLabel; } + +QgsOptionalExpression QgsFieldsProperties::DesignerTreeItemData::visibilityExpression() const +{ + return mVisibilityExpression; +} + +void QgsFieldsProperties::DesignerTreeItemData::setVisibilityExpression( const QgsOptionalExpression& visibilityExpression ) +{ + mVisibilityExpression = visibilityExpression; +} diff --git a/src/app/qgsfieldsproperties.h b/src/app/qgsfieldsproperties.h index 5af6955a557..c3d49534e9a 100644 --- a/src/app/qgsfieldsproperties.h +++ b/src/app/qgsfieldsproperties.h @@ -84,12 +84,16 @@ class APP_EXPORT QgsFieldsProperties : public QWidget, private Ui_QgsFieldsPrope bool showLabel() const; void setShowLabel( bool showLabel ); + QgsOptionalExpression visibilityExpression() const; + void setVisibilityExpression( const QgsOptionalExpression& visibilityExpression ); + private: Type mType; QString mName; int mColumnCount; bool mShowAsGroupBox; bool mShowLabel; + QgsOptionalExpression mVisibilityExpression; }; /** @@ -265,7 +269,7 @@ class DesignerTree : public QTreeWidget Q_OBJECT public: - explicit DesignerTree( QWidget* parent = nullptr ); + explicit DesignerTree( QgsVectorLayer* layer, QWidget* parent = nullptr ); QTreeWidgetItem* addItem( QTreeWidgetItem* parent, QgsFieldsProperties::DesignerTreeItemData data ); QTreeWidgetItem* addContainer( QTreeWidgetItem* parent, const QString& title , int columnCount ); @@ -282,6 +286,9 @@ class DesignerTree : public QTreeWidget private slots: void onItemDoubleClicked( QTreeWidgetItem* item, int column ); + + private: + QgsVectorLayer* mLayer; }; Q_DECLARE_METATYPE( QgsFieldsProperties::FieldConfig ) diff --git a/src/core/qgsattributeeditorelement.cpp b/src/core/qgsattributeeditorelement.cpp index f709a91a382..f8c387cde08 100644 --- a/src/core/qgsattributeeditorelement.cpp +++ b/src/core/qgsattributeeditorelement.cpp @@ -26,6 +26,19 @@ void QgsAttributeEditorContainer::setName( const QString& name ) mName = name; } +QgsOptionalExpression QgsAttributeEditorContainer::visibilityExpression() const +{ + return mVisibilityExpression; +} + +void QgsAttributeEditorContainer::setVisibilityExpression( const QgsOptionalExpression& visibilityExpression ) +{ + if ( visibilityExpression == mVisibilityExpression ) + return; + + mVisibilityExpression = visibilityExpression; +} + QList QgsAttributeEditorContainer::findElements( QgsAttributeEditorElement::AttributeEditorType type ) const { QList results; diff --git a/src/core/qgsattributeeditorelement.h b/src/core/qgsattributeeditorelement.h index c43fba54790..d404cc15126 100644 --- a/src/core/qgsattributeeditorelement.h +++ b/src/core/qgsattributeeditorelement.h @@ -17,6 +17,7 @@ #define QGSATTRIBUTEEDITORELEMENT_H #include "qgsrelation.h" +#include "qgsoptionalexpression.h" class QgsRelationManager; @@ -219,6 +220,9 @@ class CORE_EXPORT QgsAttributeEditorContainer : public QgsAttributeEditorElement */ virtual QgsAttributeEditorElement* clone( QgsAttributeEditorElement* parent ) const override; + QgsOptionalExpression visibilityExpression() const; + void setVisibilityExpression( const QgsOptionalExpression& visibilityExpression ); + private: void saveConfiguration( QDomElement& elem ) const override; QString typeIdentifier() const override; @@ -226,6 +230,7 @@ class CORE_EXPORT QgsAttributeEditorContainer : public QgsAttributeEditorElement bool mIsGroupBox; QList mChildren; int mColumnCount; + QgsOptionalExpression mVisibilityExpression; }; /** \ingroup core diff --git a/src/core/qgseditformconfig.cpp b/src/core/qgseditformconfig.cpp index bf7dc80c1b5..3f4cbd4d8d6 100644 --- a/src/core/qgseditformconfig.cpp +++ b/src/core/qgseditformconfig.cpp @@ -16,6 +16,7 @@ #include "qgseditformconfig.h" #include "qgsproject.h" #include "qgsrelationmanager.h" + //#include "qgseditorwidgetregistry.h" QgsAttributeEditorContainer::~QgsAttributeEditorContainer() @@ -573,6 +574,15 @@ QgsAttributeEditorElement* QgsEditFormConfig::attributeEditorElementFromDomEleme else container->setIsGroupBox( parent ); + bool visibilityExpressionEnabled = elem.attribute( "visibilityExpressionEnabled" ).toInt( &ok ); + QgsOptionalExpression visibilityExpression; + if ( ok ) + { + visibilityExpression.setEnabled( visibilityExpressionEnabled ); + visibilityExpression.setData( QgsExpression( elem.attribute( "visibilityExpression" ) ) ); + } + container->setVisibilityExpression( visibilityExpression ); + QDomNodeList childNodeList = elem.childNodes(); for ( int i = 0; i < childNodeList.size(); i++ ) @@ -627,6 +637,7 @@ QgsAttributeEditorElement* QgsAttributeEditorContainer::clone( QgsAttributeEdito } element->mIsGroupBox = mIsGroupBox; element->mColumnCount = mColumnCount; + element->mVisibilityExpression = mVisibilityExpression; return element; } @@ -635,6 +646,8 @@ void QgsAttributeEditorContainer::saveConfiguration( QDomElement& elem ) const { elem.setAttribute( "columnCount", mColumnCount ); elem.setAttribute( "groupBox", mIsGroupBox ? 1 : 0 ); + elem.setAttribute( "visibilityExpressionEnabled", mVisibilityExpression.enabled() ? 1 : 0 ); + elem.setAttribute( "visibilityExpression", mVisibilityExpression->expression() ); Q_FOREACH ( QgsAttributeEditorElement* child, mChildren ) { diff --git a/src/gui/qgsattributeform.cpp b/src/gui/qgsattributeform.cpp index df0c9720b0b..8e91258549d 100644 --- a/src/gui/qgsattributeform.cpp +++ b/src/gui/qgsattributeform.cpp @@ -30,6 +30,7 @@ #include "qgseditorwidgetwrapper.h" #include "qgsrelationmanager.h" #include "qgslogger.h" +#include "qgstabwidget.h" #include #include @@ -42,7 +43,6 @@ #include #include #include -#include #include #include #include @@ -747,6 +747,14 @@ void QgsAttributeForm::updateConstraints( QgsEditorWidgetWrapper *eww ) // sync ok button status synchronizeEnabledState(); + + mExpressionContext.setFeature( ft ); + + // Recheck visibility for all containers which are controlled by this value + Q_FOREACH ( ContainerInformation* info, mContainerInformationDependency.value( eww->field().name() ) ) + { + info->apply( &mExpressionContext ); + } } } @@ -810,6 +818,15 @@ void QgsAttributeForm::displayInvalidConstraintMessage( const QStringList& f, mInvalidConstraintMessage->setStyleSheet( "QLabel { background-color : #ffc800; }" ); } +void QgsAttributeForm::registerContainerInformation( QgsAttributeForm::ContainerInformation* info ) +{ + mContainerVisibilityInformation.append( info ); + Q_FOREACH ( const QString& col, info->expression.referencedColumns() ) + { + mContainerInformationDependency[ col ].append( info ); + } +} + bool QgsAttributeForm::currentFormValidConstraints( QStringList &invalidFields, QStringList &descriptions ) { @@ -1095,7 +1112,7 @@ void QgsAttributeForm::init() } } - QTabWidget* tabWidget = nullptr; + QgsTabWidget* tabWidget = nullptr; // Tab layout if ( !formWidget && mLayer->editFormConfig().layout() == QgsEditFormConfig::TabLayout ) @@ -1117,13 +1134,14 @@ void QgsAttributeForm::init() tabWidget = nullptr; WidgetInfo widgetInfo = createWidgetFromDef( widgDef, formWidget, mLayer, mContext ); layout->addWidget( widgetInfo.widget, row, column, 1, 2 ); + registerContainerInformation( new ContainerInformation( widgetInfo.widget, containerDef->visibilityExpression().data() ) ); column += 2; } else { if ( !tabWidget ) { - tabWidget = new QTabWidget(); + tabWidget = new QgsTabWidget(); layout->addWidget( tabWidget, row, column, 1, 2 ); column += 2; } @@ -1131,6 +1149,11 @@ void QgsAttributeForm::init() QWidget* tabPage = new QWidget( tabWidget ); tabWidget->addTab( tabPage, widgDef->name() ); + + if ( containerDef->visibilityExpression().enabled() ) + { + registerContainerInformation( new ContainerInformation( tabWidget, tabPage, containerDef->visibilityExpression().data() ) ); + } QGridLayout* tabPageLayout = new QGridLayout(); tabPage->setLayout( tabPageLayout ); @@ -1595,6 +1618,12 @@ QgsAttributeForm::WidgetInfo QgsAttributeForm::createWidgetFromDef( const QgsAtt { WidgetInfo widgetInfo = createWidgetFromDef( childDef, myContainer, vl, context ); + if ( childDef->type() == QgsAttributeEditorElement::AeTypeContainer ) + { + QgsAttributeEditorContainer* containerDef = static_cast( childDef ); + registerContainerInformation( new ContainerInformation( widgetInfo.widget, containerDef->visibilityExpression().data() ) ); + } + if ( widgetInfo.labelText.isNull() ) { gbLayout->addWidget( widgetInfo.widget, row, column, 1, 2 ); @@ -1875,3 +1904,22 @@ int QgsAttributeForm::messageTimeout() QSettings settings; return settings.value( "/qgis/messageTimeout", 5 ).toInt(); } + +void QgsAttributeForm::ContainerInformation::apply( QgsExpressionContext* expressionContext ) +{ + bool newVisibility = expression.evaluate( expressionContext ).toBool(); + + if ( newVisibility != isVisible ) + { + if ( tabWidget ) + { + tabWidget->setTabVisible( widget, newVisibility ); + } + else + { + widget->setVisible( newVisibility ); + } + + isVisible = newVisibility; + } +} diff --git a/src/gui/qgsattributeform.h b/src/gui/qgsattributeform.h index 623b8695cda..dafc1af5cd1 100644 --- a/src/gui/qgsattributeform.h +++ b/src/gui/qgsattributeform.h @@ -28,6 +28,7 @@ class QgsAttributeFormEditorWidget; class QgsMessageBar; class QgsMessageBarItem; class QgsWidgetWrapper; +class QgsTabWidget; /** \ingroup gui * \class QgsAttributeForm @@ -343,6 +344,37 @@ class GUI_EXPORT QgsAttributeForm : public QWidget QWidget* mSearchButtonBox; QList mInterfaces; QMap< int, QgsAttributeFormEditorWidget* > mFormEditorWidgets; + QgsExpressionContext mExpressionContext; + + struct ContainerInformation + { + ContainerInformation( QgsTabWidget* tabWidget, QWidget* widget, QgsExpression expression ) + : tabWidget( tabWidget ) + , widget( widget ) + , expression( expression ) + , isVisible( true ) + {} + + ContainerInformation( QWidget* widget, QgsExpression expression ) + : tabWidget( nullptr ) + , widget( widget ) + , expression( expression ) + , isVisible( true ) + {} + + QgsTabWidget* tabWidget; + QWidget* widget; + QgsExpression expression; + bool isVisible; + + void apply( QgsExpressionContext* expressionContext ); + }; + + void registerContainerInformation( ContainerInformation* info ); + + // Contains information about tabs and groupboxes, their visibility state visibility conditions + QVector mContainerVisibilityInformation; + QMap > mContainerInformationDependency; // Variables below are used for python static int sFormCounter; diff --git a/src/gui/qgsfieldexpressionwidget.cpp b/src/gui/qgsfieldexpressionwidget.cpp index 572913679cc..ac87a5c6547 100644 --- a/src/gui/qgsfieldexpressionwidget.cpp +++ b/src/gui/qgsfieldexpressionwidget.cpp @@ -110,6 +110,11 @@ QString QgsFieldExpressionWidget::asExpression() const return isExpression() ? currentText() : QgsExpression::quotedColumnRef( currentText() ); } +QString QgsFieldExpressionWidget::expression() const +{ + return asExpression(); +} + bool QgsFieldExpressionWidget::isValidExpression( QString *expressionError ) const { QString temp; @@ -200,6 +205,11 @@ void QgsFieldExpressionWidget::setField( const QString &fieldName ) currentFieldChanged(); } +void QgsFieldExpressionWidget::setExpression( const QString& expression ) +{ + setField( expression ); +} + void QgsFieldExpressionWidget::editExpression() { QString currentExpression = currentText(); diff --git a/src/gui/qgsfieldexpressionwidget.h b/src/gui/qgsfieldexpressionwidget.h index 901cdcc2b9b..ab263330571 100644 --- a/src/gui/qgsfieldexpressionwidget.h +++ b/src/gui/qgsfieldexpressionwidget.h @@ -91,13 +91,25 @@ class GUI_EXPORT QgsFieldExpressionWidget : public QWidget */ QString currentText() const; - /** Returns the currently selected field or expression. If a field is currently selected, the returned + /** + * Returns the currently selected field or expression. If a field is currently selected, the returned * value will be converted to a valid expression referencing this field (ie enclosing the field name with * appropriate quotations). * @note added in QGIS 2.14 */ QString asExpression() const; + /** + * Returns the currently selected field or expression. If a field is currently selected, the returned + * value will be converted to a valid expression referencing this field (ie enclosing the field name with + * appropriate quotations). + * + * Alias for asExpression() + * + * @note added in QGIS 3.0 + */ + QString expression() const; + //! Returns the currently used layer QgsVectorLayer* layer() const; @@ -132,6 +144,14 @@ class GUI_EXPORT QgsFieldExpressionWidget : public QWidget //! sets the current field or expression in the widget void setField( const QString &fieldName ); + /** + * Sets the current expression text and if applicable also the field. + * Alias for setField. + * + * @note Added in QGIS 3.0 + */ + void setExpression( const QString& expression ); + protected slots: //! open the expression dialog to edit the current or add a new expression void editExpression();