[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.
This commit is contained in:
Matthias Kuhn 2016-09-08 12:19:23 +02:00
parent ac41436de3
commit 1882cabea9
9 changed files with 186 additions and 8 deletions

View File

@ -28,6 +28,7 @@
#include "qgsrelationmanager.h"
#include "qgsvectordataprovider.h"
#include "qgsvectorlayer.h"
#include "qgsfieldexpressionwidget.h"
#include <QTreeWidgetItem>
#include <QWidget>
@ -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;
}

View File

@ -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 )

View File

@ -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<QgsAttributeEditorElement*> QgsAttributeEditorContainer::findElements( QgsAttributeEditorElement::AttributeEditorType type ) const
{
QList<QgsAttributeEditorElement*> results;

View File

@ -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<QgsAttributeEditorElement*> mChildren;
int mColumnCount;
QgsOptionalExpression mVisibilityExpression;
};
/** \ingroup core

View File

@ -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 )
{

View File

@ -30,6 +30,7 @@
#include "qgseditorwidgetwrapper.h"
#include "qgsrelationmanager.h"
#include "qgslogger.h"
#include "qgstabwidget.h"
#include <QDir>
#include <QTextStream>
@ -42,7 +43,6 @@
#include <QLabel>
#include <QPushButton>
#include <QScrollArea>
#include <QTabWidget>
#include <QUiLoader>
#include <QMessageBox>
#include <QSettings>
@ -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<QgsAttributeEditorContainer*>( 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;
}
}

View File

@ -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<QgsAttributeFormInterface*> 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<ContainerInformation*> mContainerVisibilityInformation;
QMap<QString, QVector<ContainerInformation*> > mContainerInformationDependency;
// Variables below are used for python
static int sFormCounter;

View File

@ -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();

View File

@ -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();