mirror of
https://github.com/qgis/QGIS.git
synced 2025-11-18 00:14:20 -05:00
Merge pull request #51309 from nirvn/validity_table
[attributes table] Add constraint-based conditional styling and failing constraints feature filter
This commit is contained in:
commit
8e12541dfd
@ -369,6 +369,7 @@
|
||||
<file>themes/default/mActionOpenTable.svg</file>
|
||||
<file>themes/default/mActionOpenTableEdited.svg</file>
|
||||
<file>themes/default/mActionOpenTableSelected.svg</file>
|
||||
<file>themes/default/mActionOpenTableInvalid.svg</file>
|
||||
<file>themes/default/mActionOpenTableVisible.svg</file>
|
||||
<file>themes/default/mActionAddTable.svg</file>
|
||||
<file>themes/default/mActionOffsetPointSymbols.svg</file>
|
||||
|
||||
2
images/themes/default/mActionOpenTableInvalid.svg
Normal file
2
images/themes/default/mActionOpenTableInvalid.svg
Normal file
@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><metadata><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/></cc:Work></rdf:RDF></metadata><g stroke="#6e97c4"><path d="m2.5 7.5h19v14h-19z" fill="#e6e6e6"/><path d="m2.5 2.5h19v5h-19z" fill="#bad9ec"/><path d="m5 5h4" fill="none" stroke-width="2"/><path d="m11 5h8" fill="none" stroke-width="2"/></g><g transform="translate(0,-8)" fill="none" stroke="#6e97c4"><path d="m5 18.7h4"/><path d="m11 18.7h8" stroke="#6e97c4"/><path d="m5 21.7h4"/><path d="m11 21.7h8"/><path d="m5 24.7h4" stroke="#6e97c4"/><path d="m11 24.7h8"/></g><text transform="scale(1 .99998)" x="7.3536935" y="22.897999" fill="#fe9800" font-family="'Wide Latin'" font-size="21.505px" stroke-width=".53763" style="line-height:1.25" xml:space="preserve"><tspan x="7.3536935" y="22.897999" stroke-width=".53763">✘</tspan></text></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@ -8,8 +8,8 @@
|
||||
|
||||
|
||||
|
||||
typedef QList<QgsConditionalStyle> QgsConditionalStyles;
|
||||
|
||||
typedef QList<QgsConditionalStyle> QgsConditionalStyles;
|
||||
|
||||
class QgsConditionalLayerStyles : QObject
|
||||
{
|
||||
@ -43,12 +43,24 @@ Each row will check be checked against each rule.
|
||||
.. seealso:: :py:func:`rowStyles`
|
||||
|
||||
.. versionadded:: 2.12
|
||||
%End
|
||||
|
||||
QgsConditionalStyle constraintFailureStyles( QgsFieldConstraints::ConstraintStrength strength );
|
||||
%Docstring
|
||||
Returns a style associated to a constraint failure.
|
||||
|
||||
:param strength: the type of constraint
|
||||
|
||||
.. versionadded:: 3.30
|
||||
%End
|
||||
|
||||
void setFieldStyles( const QString &fieldName, const QList<QgsConditionalStyle> &styles );
|
||||
%Docstring
|
||||
Set the conditional ``styles`` for a field, with the specified ``fieldName``.
|
||||
|
||||
The field value is inserted into a 'value' variable to conduct
|
||||
expression checks.
|
||||
|
||||
.. seealso:: :py:func:`fieldStyles`
|
||||
%End
|
||||
|
||||
|
||||
@ -168,6 +168,16 @@ be unique within regard to ``existingValues``.
|
||||
The optional seed value can be used as a basis for generated values.
|
||||
|
||||
.. versionadded:: 3.6
|
||||
%End
|
||||
|
||||
static bool attributeHasConstraints( const QgsVectorLayer *layer, int attributeIndex );
|
||||
%Docstring
|
||||
Returns ``True`` if a feature attribute has active constraints.
|
||||
|
||||
:param layer: the vector layer from which field constraints will be checked for
|
||||
:param attributeIndex: the attribute index
|
||||
|
||||
.. versionadded:: 3.30
|
||||
%End
|
||||
|
||||
static bool validateAttribute( const QgsVectorLayer *layer, const QgsFeature &feature, int attributeIndex, QStringList &errors /Out/,
|
||||
|
||||
@ -24,7 +24,8 @@ class QgsAttributeTableFilterModel: QSortFilterProxyModel, QgsFeatureModel
|
||||
ShowSelected,
|
||||
ShowVisible,
|
||||
ShowFilteredList,
|
||||
ShowEdited
|
||||
ShowEdited,
|
||||
ShowInvalid,
|
||||
};
|
||||
|
||||
enum ColumnType
|
||||
|
||||
@ -247,6 +247,22 @@ Any extra columns need to be implemented by proxy models in front of this model.
|
||||
%Docstring
|
||||
Empty extra columns to announce from this model.
|
||||
Any extra columns need to be implemented by proxy models in front of this model.
|
||||
%End
|
||||
|
||||
bool showValidityState() const;
|
||||
%Docstring
|
||||
Returns whether the attribute table will add a visual feedback to cells when an attribute
|
||||
constraint is not met.
|
||||
|
||||
.. versionadded:: 3.30
|
||||
%End
|
||||
|
||||
void setShowValidityState( bool show );
|
||||
%Docstring
|
||||
Sets whether the attribute table will add a visual feedback to cells when an attribute constraint
|
||||
is not met.
|
||||
|
||||
.. versionadded:: 3.30
|
||||
%End
|
||||
|
||||
public slots:
|
||||
|
||||
@ -23,7 +23,35 @@
|
||||
|
||||
QgsConditionalLayerStyles::QgsConditionalLayerStyles( QObject *parent )
|
||||
: QObject( parent )
|
||||
{}
|
||||
{
|
||||
}
|
||||
|
||||
QgsConditionalStyle QgsConditionalLayerStyles::constraintFailureStyles( QgsFieldConstraints::ConstraintStrength strength )
|
||||
{
|
||||
switch ( strength )
|
||||
{
|
||||
case QgsFieldConstraints::ConstraintStrengthHard:
|
||||
{
|
||||
QgsConditionalStyle hardConstraintFailureStyle;
|
||||
hardConstraintFailureStyle.setBackgroundColor( QColor( 255, 152, 0 ) );
|
||||
hardConstraintFailureStyle.setTextColor( QColor( 0, 0, 0 ) );
|
||||
return hardConstraintFailureStyle;
|
||||
}
|
||||
|
||||
case QgsFieldConstraints::ConstraintStrengthSoft:
|
||||
{
|
||||
QgsConditionalStyle softConstraintFailureStyle;
|
||||
softConstraintFailureStyle.setBackgroundColor( QColor( 255, 191, 12 ) );
|
||||
softConstraintFailureStyle.setTextColor( QColor( 0, 0, 0 ) );
|
||||
return softConstraintFailureStyle;
|
||||
}
|
||||
|
||||
case QgsFieldConstraints::ConstraintStrengthNotSet:
|
||||
return QgsConditionalStyle();
|
||||
}
|
||||
|
||||
return QgsConditionalStyle();
|
||||
}
|
||||
|
||||
QgsConditionalStyles QgsConditionalLayerStyles::rowStyles() const
|
||||
{
|
||||
@ -56,13 +84,13 @@ QList<QgsConditionalStyle> QgsConditionalLayerStyles::fieldStyles( const QString
|
||||
bool QgsConditionalLayerStyles::writeXml( QDomNode &node, QDomDocument &doc, const QgsReadWriteContext &context ) const
|
||||
{
|
||||
QDomElement stylesel = doc.createElement( QStringLiteral( "conditionalstyles" ) );
|
||||
|
||||
QDomElement rowel = doc.createElement( QStringLiteral( "rowstyles" ) );
|
||||
const auto constMRowStyles = mRowStyles;
|
||||
for ( const QgsConditionalStyle &style : constMRowStyles )
|
||||
{
|
||||
style.writeXml( rowel, doc, context );
|
||||
}
|
||||
|
||||
stylesel.appendChild( rowel );
|
||||
|
||||
QDomElement fieldsel = doc.createElement( QStringLiteral( "fieldstyles" ) );
|
||||
@ -79,7 +107,6 @@ bool QgsConditionalLayerStyles::writeXml( QDomNode &node, QDomDocument &doc, con
|
||||
}
|
||||
fieldsel.appendChild( fieldel );
|
||||
}
|
||||
|
||||
stylesel.appendChild( fieldsel );
|
||||
|
||||
node.appendChild( stylesel );
|
||||
@ -100,9 +127,11 @@ bool QgsConditionalLayerStyles::rulesNeedGeometry() const
|
||||
|
||||
bool QgsConditionalLayerStyles::readXml( const QDomNode &node, const QgsReadWriteContext &context )
|
||||
{
|
||||
const QDomElement condel = node.firstChildElement( QStringLiteral( "conditionalstyles" ) );
|
||||
mRowStyles.clear();
|
||||
mFieldStyles.clear();
|
||||
|
||||
const QDomElement condel = node.firstChildElement( QStringLiteral( "conditionalstyles" ) );
|
||||
|
||||
const QDomElement rowstylesel = condel.firstChildElement( QStringLiteral( "rowstyles" ) );
|
||||
QDomNodeList nodelist = rowstylesel.toElement().elementsByTagName( QStringLiteral( "style" ) );
|
||||
for ( int i = 0; i < nodelist.count(); i++ )
|
||||
|
||||
@ -16,6 +16,8 @@
|
||||
#define QGSCONDITIONALSTYLE_H
|
||||
|
||||
#include "qgis_core.h"
|
||||
#include "qgsfield.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QFont>
|
||||
#include <QColor>
|
||||
@ -32,7 +34,6 @@ class QgsSymbol;
|
||||
|
||||
typedef QList<QgsConditionalStyle> QgsConditionalStyles;
|
||||
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* \brief The QgsConditionalLayerStyles class holds conditional style information
|
||||
@ -65,9 +66,19 @@ class CORE_EXPORT QgsConditionalLayerStyles : public QObject
|
||||
*/
|
||||
void setRowStyles( const QgsConditionalStyles &styles );
|
||||
|
||||
/**
|
||||
* Returns a style associated to a constraint failure.
|
||||
* \param strength the type of constraint
|
||||
* \since QGIS 3.30
|
||||
*/
|
||||
QgsConditionalStyle constraintFailureStyles( QgsFieldConstraints::ConstraintStrength strength );
|
||||
|
||||
/**
|
||||
* Set the conditional \a styles for a field, with the specified \a fieldName.
|
||||
*
|
||||
* The field value is inserted into a 'value' variable to conduct
|
||||
* expression checks.
|
||||
*
|
||||
* \see fieldStyles()
|
||||
*/
|
||||
void setFieldStyles( const QString &fieldName, const QList<QgsConditionalStyle> &styles );
|
||||
|
||||
@ -367,6 +367,20 @@ QVariant QgsVectorLayerUtils::createUniqueValueFromCache( const QgsVectorLayer *
|
||||
|
||||
}
|
||||
|
||||
bool QgsVectorLayerUtils::attributeHasConstraints( const QgsVectorLayer *layer, int attributeIndex )
|
||||
{
|
||||
if ( !layer )
|
||||
return false;
|
||||
|
||||
if ( attributeIndex < 0 || attributeIndex >= layer->fields().count() )
|
||||
return false;
|
||||
|
||||
const QgsFieldConstraints constraints = layer->fields().at( attributeIndex ).constraints();
|
||||
return ( constraints.constraints() & QgsFieldConstraints::ConstraintNotNull ||
|
||||
constraints.constraints() & QgsFieldConstraints::ConstraintUnique ||
|
||||
constraints.constraints() & QgsFieldConstraints::ConstraintExpression );
|
||||
}
|
||||
|
||||
bool QgsVectorLayerUtils::validateAttribute( const QgsVectorLayer *layer, const QgsFeature &feature, int attributeIndex, QStringList &errors,
|
||||
QgsFieldConstraints::ConstraintStrength strength, QgsFieldConstraints::ConstraintOrigin origin )
|
||||
{
|
||||
|
||||
@ -183,6 +183,14 @@ class CORE_EXPORT QgsVectorLayerUtils
|
||||
*/
|
||||
static QVariant createUniqueValueFromCache( const QgsVectorLayer *layer, int fieldIndex, const QSet<QVariant> &existingValues, const QVariant &seed = QVariant() );
|
||||
|
||||
/**
|
||||
* Returns TRUE if a feature attribute has active constraints.
|
||||
* \param layer the vector layer from which field constraints will be checked for
|
||||
* \param attributeIndex the attribute index
|
||||
* \since QGIS 3.30
|
||||
*/
|
||||
static bool attributeHasConstraints( const QgsVectorLayer *layer, int attributeIndex );
|
||||
|
||||
/**
|
||||
* Tests a feature attribute value to check whether it passes all constraints which are present on the corresponding field.
|
||||
* Returns TRUE if the attribute value is valid for the field. Any constraint failures will be reported in the errors argument.
|
||||
|
||||
@ -313,7 +313,11 @@ bool QgsAttributeTableFilterModel::selectedOnTop()
|
||||
void QgsAttributeTableFilterModel::setFilteredFeatures( const QgsFeatureIds &ids )
|
||||
{
|
||||
mFilteredFeatures = ids;
|
||||
if ( mFilterMode != ShowFilteredList &&
|
||||
mFilterMode != ShowInvalid )
|
||||
{
|
||||
setFilterMode( ShowFilteredList );
|
||||
}
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
@ -337,6 +341,11 @@ void QgsAttributeTableFilterModel::setFilterMode( FilterMode filterMode )
|
||||
connectFilterModeConnections( filterMode );
|
||||
mFilterMode = filterMode;
|
||||
invalidate();
|
||||
|
||||
if ( mFilterMode == QgsAttributeTableFilterModel::ShowInvalid )
|
||||
{
|
||||
filterFeatures();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -356,6 +365,7 @@ void QgsAttributeTableFilterModel::disconnectFilterModeConnections()
|
||||
case ShowSelected:
|
||||
break;
|
||||
case ShowFilteredList:
|
||||
case ShowInvalid:
|
||||
disconnect( layer(), &QgsVectorLayer::featureAdded, this, &QgsAttributeTableFilterModel::startTimedFilterFeatures );
|
||||
disconnect( layer(), &QgsVectorLayer::attributeValueChanged, this, &QgsAttributeTableFilterModel::onAttributeValueChanged );
|
||||
disconnect( layer(), &QgsVectorLayer::geometryChanged, this, &QgsAttributeTableFilterModel::onGeometryChanged );
|
||||
@ -380,6 +390,7 @@ void QgsAttributeTableFilterModel::connectFilterModeConnections( QgsAttributeTab
|
||||
case ShowSelected:
|
||||
break;
|
||||
case ShowFilteredList:
|
||||
case ShowInvalid:
|
||||
connect( layer(), &QgsVectorLayer::featureAdded, this, &QgsAttributeTableFilterModel::startTimedFilterFeatures );
|
||||
connect( layer(), &QgsVectorLayer::attributeValueChanged, this, &QgsAttributeTableFilterModel::onAttributeValueChanged );
|
||||
connect( layer(), &QgsVectorLayer::geometryChanged, this, &QgsAttributeTableFilterModel::onGeometryChanged );
|
||||
@ -396,6 +407,7 @@ bool QgsAttributeTableFilterModel::filterAcceptsRow( int sourceRow, const QModel
|
||||
return true;
|
||||
|
||||
case ShowFilteredList:
|
||||
case ShowInvalid:
|
||||
return mFilteredFeatures.contains( masterModel()->rowToId( sourceRow ) );
|
||||
|
||||
case ShowSelected:
|
||||
@ -449,7 +461,7 @@ void QgsAttributeTableFilterModel::onAttributeValueChanged( QgsFeatureId fid, in
|
||||
Q_UNUSED( fid );
|
||||
Q_UNUSED( value );
|
||||
|
||||
if ( mFilterExpression.referencedAttributeIndexes( layer()->fields() ).contains( idx ) )
|
||||
if ( mFilterMode == QgsAttributeTableFilterModel::ShowInvalid || mFilterExpression.referencedAttributeIndexes( layer()->fields() ).contains( idx ) )
|
||||
{
|
||||
startTimedFilterFeatures();
|
||||
}
|
||||
@ -457,7 +469,7 @@ void QgsAttributeTableFilterModel::onAttributeValueChanged( QgsFeatureId fid, in
|
||||
|
||||
void QgsAttributeTableFilterModel::onGeometryChanged()
|
||||
{
|
||||
if ( mFilterExpression.needsGeometry() )
|
||||
if ( mFilterMode == QgsAttributeTableFilterModel::ShowInvalid || mFilterExpression.needsGeometry() )
|
||||
{
|
||||
startTimedFilterFeatures();
|
||||
}
|
||||
|
||||
@ -48,7 +48,8 @@ class GUI_EXPORT QgsAttributeTableFilterModel: public QSortFilterProxyModel, pub
|
||||
ShowSelected, //!< Show only selected features
|
||||
ShowVisible, //!< Show only visible features (depends on the map canvas)
|
||||
ShowFilteredList, //!< Show only features whose ids are on the filter list. {\see setFilteredFeatures}
|
||||
ShowEdited //!< Show only features which have unsaved changes
|
||||
ShowEdited, //!< Show only features which have unsaved changes
|
||||
ShowInvalid, //!< Show only features not respecting constraints (since QGIS 3.30)
|
||||
};
|
||||
Q_ENUM( FilterMode )
|
||||
|
||||
|
||||
@ -511,6 +511,7 @@ void QgsAttributeTableModel::fieldConditionalStyleChanged( const QString &fieldN
|
||||
if ( fieldName.isNull() )
|
||||
{
|
||||
mRowStylesMap.clear();
|
||||
mConstraintStylesMap.clear();
|
||||
emit dataChanged( index( 0, 0 ), index( rowCount() - 1, columnCount() - 1 ) );
|
||||
return;
|
||||
}
|
||||
@ -749,11 +750,38 @@ QVariant QgsAttributeTableModel::data( const QModelIndex &index, int role ) cons
|
||||
styles = QgsConditionalStyle::matchingConditionalStyles( mLayer->conditionalStyles()->rowStyles(), QVariant(), mExpressionContext );
|
||||
mRowStylesMap.insert( mFeat.id(), styles );
|
||||
}
|
||||
|
||||
const QgsConditionalStyle rowstyle = QgsConditionalStyle::compressStyles( styles );
|
||||
|
||||
QgsConditionalStyle constraintstyle;
|
||||
if ( mShowValidityState && QgsVectorLayerUtils::attributeHasConstraints( mLayer, fieldId ) )
|
||||
{
|
||||
if ( mConstraintStylesMap.contains( mFeat.id() ) &&
|
||||
mConstraintStylesMap[mFeat.id()].contains( fieldId ) )
|
||||
{
|
||||
constraintstyle = mConstraintStylesMap[mFeat.id()][fieldId];
|
||||
}
|
||||
else
|
||||
{
|
||||
QStringList errors;
|
||||
if ( !QgsVectorLayerUtils::validateAttribute( mLayer, mFeat, fieldId, errors, QgsFieldConstraints::ConstraintStrengthHard ) )
|
||||
{
|
||||
constraintstyle = mLayer->conditionalStyles()->constraintFailureStyles( QgsFieldConstraints::ConstraintStrengthHard );
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( !QgsVectorLayerUtils::validateAttribute( mLayer, mFeat, fieldId, errors, QgsFieldConstraints::ConstraintStrengthSoft ) )
|
||||
{
|
||||
constraintstyle = mLayer->conditionalStyles()->constraintFailureStyles( QgsFieldConstraints::ConstraintStrengthSoft );
|
||||
}
|
||||
}
|
||||
mConstraintStylesMap[mFeat.id()].insert( fieldId, constraintstyle );
|
||||
}
|
||||
}
|
||||
|
||||
styles = mLayer->conditionalStyles()->fieldStyles( field.name() );
|
||||
styles = QgsConditionalStyle::matchingConditionalStyles( styles, val, mExpressionContext );
|
||||
styles.insert( 0, rowstyle );
|
||||
styles.insert( 0, constraintstyle );
|
||||
const QgsConditionalStyle style = QgsConditionalStyle::compressStyles( styles );
|
||||
|
||||
if ( style.isValid() )
|
||||
@ -796,6 +824,7 @@ bool QgsAttributeTableModel::setData( const QModelIndex &index, const QVariant &
|
||||
return false;
|
||||
|
||||
mRowStylesMap.remove( mFeat.id() );
|
||||
mConstraintStylesMap.remove( mFeat.id() );
|
||||
|
||||
if ( !mLayer->isModified() )
|
||||
return false;
|
||||
|
||||
@ -256,6 +256,20 @@ class GUI_EXPORT QgsAttributeTableModel: public QAbstractTableModel
|
||||
*/
|
||||
void setExtraColumns( int extraColumns );
|
||||
|
||||
/**
|
||||
* Returns whether the attribute table will add a visual feedback to cells when an attribute
|
||||
* constraint is not met.
|
||||
* \since QGIS 3.30
|
||||
*/
|
||||
bool showValidityState() const { return mShowValidityState; }
|
||||
|
||||
/**
|
||||
* Sets whether the attribute table will add a visual feedback to cells when an attribute constraint
|
||||
* is not met.
|
||||
* \since QGIS 3.30
|
||||
*/
|
||||
void setShowValidityState( bool show ) { mShowValidityState = show; }
|
||||
|
||||
public slots:
|
||||
|
||||
/**
|
||||
@ -350,6 +364,7 @@ class GUI_EXPORT QgsAttributeTableModel: public QAbstractTableModel
|
||||
QHash<QgsFeatureId, int> mIdRowMap;
|
||||
QHash<int, QgsFeatureId> mRowIdMap;
|
||||
mutable QHash<QgsFeatureId, QList<QgsConditionalStyle> > mRowStylesMap;
|
||||
mutable QHash<QgsFeatureId, QHash<int, QgsConditionalStyle> > mConstraintStylesMap;
|
||||
|
||||
mutable QgsExpressionContext mExpressionContext;
|
||||
|
||||
@ -412,6 +427,8 @@ class GUI_EXPORT QgsAttributeTableModel: public QAbstractTableModel
|
||||
//! TRUE if triggered by afterRollback()
|
||||
bool mIsCleaningUpAfterRollback = false;
|
||||
|
||||
bool mShowValidityState = false;
|
||||
|
||||
friend class TestQgsAttributeTable;
|
||||
|
||||
};
|
||||
|
||||
@ -326,6 +326,10 @@ void QgsDualView::setFilterMode( QgsAttributeTableFilterModel::FilterMode filter
|
||||
case QgsAttributeTableFilterModel::ShowSelected:
|
||||
disconnect( masterModel()->layer(), &QgsVectorLayer::selectionChanged, this, &QgsDualView::updateSelectedFeatures );
|
||||
break;
|
||||
|
||||
case QgsAttributeTableFilterModel::ShowInvalid:
|
||||
mMasterModel->setShowValidityState( false );
|
||||
break;
|
||||
}
|
||||
|
||||
QgsFeatureRequest r = mMasterModel->request();
|
||||
@ -363,6 +367,16 @@ void QgsDualView::setFilterMode( QgsAttributeTableFilterModel::FilterMode filter
|
||||
connect( masterModel()->layer(), &QgsVectorLayer::layerModified, this, &QgsDualView::updateEditedAddedFeatures );
|
||||
break;
|
||||
|
||||
case QgsAttributeTableFilterModel::ShowInvalid:
|
||||
{
|
||||
mMasterModel->setShowValidityState( true );
|
||||
const QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer ) );
|
||||
filterFeatures( QStringLiteral( "is_feature_valid() = false" ), context );
|
||||
connect( mFilterModel, &QgsAttributeTableFilterModel::featuresFiltered, this, &QgsDualView::filterChanged );
|
||||
connect( mFilterModel, &QgsAttributeTableFilterModel::filterError, this, &QgsDualView::filterError );
|
||||
break;
|
||||
}
|
||||
|
||||
case QgsAttributeTableFilterModel::ShowAll:
|
||||
case QgsAttributeTableFilterModel::ShowFilteredList:
|
||||
{
|
||||
@ -390,6 +404,7 @@ void QgsDualView::setFilterMode( QgsAttributeTableFilterModel::FilterMode filter
|
||||
case QgsAttributeTableFilterModel::ShowAll:
|
||||
case QgsAttributeTableFilterModel::ShowEdited:
|
||||
case QgsAttributeTableFilterModel::ShowFilteredList:
|
||||
case QgsAttributeTableFilterModel::ShowInvalid:
|
||||
case QgsAttributeTableFilterModel::ShowSelected:
|
||||
setBrowsingAutoPanScaleAllowed( true );
|
||||
break;
|
||||
|
||||
@ -55,6 +55,7 @@ QgsFeatureFilterWidget::QgsFeatureFilterWidget( QWidget *parent )
|
||||
mActionShowAllFilter->setIcon( QgsApplication::getThemeIcon( "/mActionOpenTable.svg" ) );
|
||||
mActionAdvancedFilter->setIcon( QgsApplication::getThemeIcon( "/mActionFilter2.svg" ) );
|
||||
mActionSelectedFilter->setIcon( QgsApplication::getThemeIcon( "/mActionOpenTableSelected.svg" ) );
|
||||
mActionInvalidFilter->setIcon( QgsApplication::getThemeIcon( "/mActionOpenTableInvalid.svg" ) );
|
||||
mActionVisibleFilter->setIcon( QgsApplication::getThemeIcon( "/mActionOpenTableVisible.svg" ) );
|
||||
mActionEditedFilter->setIcon( QgsApplication::getThemeIcon( "/mActionOpenTableEdited.svg" ) );
|
||||
|
||||
@ -70,6 +71,7 @@ QgsFeatureFilterWidget::QgsFeatureFilterWidget( QWidget *parent )
|
||||
connect( mActionAdvancedFilter, &QAction::triggered, this, &QgsFeatureFilterWidget::filterExpressionBuilder );
|
||||
connect( mActionShowAllFilter, &QAction::triggered, this, &QgsFeatureFilterWidget::filterShowAll );
|
||||
connect( mActionSelectedFilter, &QAction::triggered, this, &QgsFeatureFilterWidget::filterSelected );
|
||||
connect( mActionInvalidFilter, &QAction::triggered, this, &QgsFeatureFilterWidget::filterInvalid );
|
||||
connect( mActionVisibleFilter, &QAction::triggered, this, &QgsFeatureFilterWidget::filterVisible );
|
||||
connect( mActionEditedFilter, &QAction::triggered, this, &QgsFeatureFilterWidget::filterEdited );
|
||||
connect( mFilterQuery, &QLineEdit::returnPressed, this, &QgsFeatureFilterWidget::filterQueryAccepted );
|
||||
@ -138,6 +140,18 @@ void QgsFeatureFilterWidget::filterVisible()
|
||||
mMainView->setFilterMode( QgsAttributeTableFilterModel::ShowVisible );
|
||||
}
|
||||
|
||||
void QgsFeatureFilterWidget::filterInvalid()
|
||||
{
|
||||
mFilterButton->setDefaultAction( mActionInvalidFilter );
|
||||
mFilterButton->setPopupMode( QToolButton::InstantPopup );
|
||||
mFilterQuery->setVisible( false );
|
||||
mApplyFilterButton->setVisible( false );
|
||||
mStoreFilterExpressionButton->setVisible( false );
|
||||
const QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer ) );
|
||||
mMainView->filterFeatures( QStringLiteral( "is_feature_valid() = false" ), context );
|
||||
mMainView->setFilterMode( QgsAttributeTableFilterModel::ShowInvalid );
|
||||
}
|
||||
|
||||
void QgsFeatureFilterWidget::filterEdited()
|
||||
{
|
||||
mFilterButton->setDefaultAction( mActionEditedFilter );
|
||||
@ -192,6 +206,7 @@ void QgsFeatureFilterWidget::columnBoxInit()
|
||||
{
|
||||
mFilterButton->addAction( mActionVisibleFilter );
|
||||
}
|
||||
mFilterButton->addAction( mActionInvalidFilter );
|
||||
mFilterButton->addAction( mActionEditedFilter );
|
||||
mFilterButton->addAction( mActionFilterColumnsMenu );
|
||||
mFilterButton->addAction( mActionAdvancedFilter );
|
||||
|
||||
@ -69,6 +69,7 @@ class GUI_EXPORT QgsFeatureFilterWidget : public QWidget, private Ui::QgsFeature
|
||||
public slots:
|
||||
void filterShowAll();
|
||||
void filterSelected();
|
||||
void filterInvalid();
|
||||
void filterVisible();
|
||||
void filterEdited();
|
||||
|
||||
|
||||
@ -34,8 +34,8 @@ QgsFieldConditionalFormatWidget::QgsFieldConditionalFormatWidget( QWidget *paren
|
||||
setupUi( this );
|
||||
setPanelTitle( tr( "Conditional Styles" ) );
|
||||
connect( mFieldCombo, &QgsFieldComboBox::fieldChanged, this, &QgsFieldConditionalFormatWidget::fieldChanged );
|
||||
connect( fieldRadio, &QAbstractButton::clicked, this, &QgsFieldConditionalFormatWidget::reloadStyles );
|
||||
connect( rowRadio, &QAbstractButton::clicked, this, &QgsFieldConditionalFormatWidget::reloadStyles );
|
||||
connect( fieldRadio, &QAbstractButton::clicked, this, &QgsFieldConditionalFormatWidget::typeChanged );
|
||||
connect( rowRadio, &QAbstractButton::clicked, this, &QgsFieldConditionalFormatWidget::typeChanged );
|
||||
connect( mNewButton, &QAbstractButton::clicked, this, &QgsFieldConditionalFormatWidget::addNewRule );
|
||||
connect( listView, &QAbstractItemView::clicked, this, &QgsFieldConditionalFormatWidget::ruleClicked );
|
||||
mModel = new QStandardItemModel( listView );
|
||||
@ -103,10 +103,11 @@ void QgsFieldConditionalFormatWidget::editStyle( int editIndex, const QgsConditi
|
||||
fieldName = mFieldCombo->currentField();
|
||||
mLayer->conditionalStyles()->setFieldStyles( fieldName, styles );
|
||||
}
|
||||
if ( rowRadio->isChecked() )
|
||||
else if ( rowRadio->isChecked() )
|
||||
{
|
||||
mLayer->conditionalStyles()->setRowStyles( styles );
|
||||
}
|
||||
|
||||
reloadStyles();
|
||||
emit rulesUpdated( fieldName );
|
||||
} );
|
||||
@ -142,10 +143,11 @@ QList<QgsConditionalStyle> QgsFieldConditionalFormatWidget::getStyles()
|
||||
{
|
||||
styles = mLayer->conditionalStyles()->fieldStyles( mFieldCombo->currentField() );
|
||||
}
|
||||
if ( rowRadio->isChecked() )
|
||||
else if ( rowRadio->isChecked() )
|
||||
{
|
||||
styles = mLayer->conditionalStyles()->rowStyles();
|
||||
}
|
||||
|
||||
return styles;
|
||||
}
|
||||
|
||||
@ -187,6 +189,11 @@ QList<QgsConditionalStyle> QgsFieldConditionalFormatWidget::defaultPresets()
|
||||
return styles;
|
||||
}
|
||||
|
||||
void QgsFieldConditionalFormatWidget::typeChanged()
|
||||
{
|
||||
reloadStyles();
|
||||
}
|
||||
|
||||
void QgsFieldConditionalFormatWidget::reloadStyles()
|
||||
{
|
||||
mModel->clear();
|
||||
@ -224,7 +231,7 @@ void QgsFieldConditionalFormatWidget::deleteCurrentRule()
|
||||
fieldName = mFieldCombo->currentField();
|
||||
mLayer->conditionalStyles()->setFieldStyles( fieldName, styles );
|
||||
}
|
||||
if ( rowRadio->isChecked() )
|
||||
else if ( rowRadio->isChecked() )
|
||||
{
|
||||
mLayer->conditionalStyles()->setRowStyles( styles );
|
||||
}
|
||||
|
||||
@ -110,6 +110,7 @@ class GUI_EXPORT QgsFieldConditionalFormatWidget : public QgsPanelWidget, privat
|
||||
|
||||
private slots:
|
||||
|
||||
void typeChanged();
|
||||
void ruleClicked( const QModelIndex &index );
|
||||
void reloadStyles();
|
||||
void addNewRule();
|
||||
|
||||
@ -198,7 +198,16 @@
|
||||
<normaloff>:/images/themes/default/mActionOpenTableVisible.svg</normaloff>:/images/themes/default/mActionOpenTableVisible.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Show Features Visible On Map</string>
|
||||
<string>Show Features Visible on Map</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="mActionInvalidFilter">
|
||||
<property name="icon">
|
||||
<iconset resource="../../images/images.qrc">
|
||||
<normaloff>:/images/themes/default/mActionOpenTableInvalid.svg</normaloff>:/images/themes/default/mActionOpenTableInvalid.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Show Features with Failing Constraints</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="mActionEditedFilter">
|
||||
|
||||
@ -21,18 +21,15 @@
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0,2">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetDefaultConstraint</enum>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="3" rowspan="2">
|
||||
<item row="2" column="0">
|
||||
<widget class="QPushButton" name="mNewButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
@ -45,17 +42,9 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QgsFieldComboBox" name="mFieldCombo"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="fieldRadio">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
@ -71,6 +60,18 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QgsFieldComboBox" name="mFieldCombo">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QRadioButton" name="rowRadio">
|
||||
<property name="sizePolicy">
|
||||
|
||||
@ -67,6 +67,7 @@ class TestQgsAttributeTable : public QObject
|
||||
void testSortNumbers();
|
||||
void testStartMultiEditNoChanges();
|
||||
void testMultiEditMakeUncommittedChanges();
|
||||
void testInvalidView();
|
||||
|
||||
private:
|
||||
QgisApp *mQgisApp = nullptr;
|
||||
@ -793,5 +794,43 @@ void TestQgsAttributeTable::testOpenWithFilterExpression()
|
||||
QCOMPARE( dlg->mMainView->filteredFeatures(), QgsFeatureIds() << 1 << 3 );
|
||||
}
|
||||
|
||||
void TestQgsAttributeTable::testInvalidView()
|
||||
{
|
||||
std::unique_ptr< QgsVectorLayer> tempLayer( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:4326&field=pk:int&field=col1:date" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
|
||||
QVERIFY( tempLayer->isValid() );
|
||||
|
||||
QgsPolylineXY line;
|
||||
line << QgsPointXY( 0, 0 ) << QgsPointXY( 1, 1 );
|
||||
QgsGeometry geometry = QgsGeometry::fromPolylineXY( line ) ;
|
||||
QgsFeature f1( tempLayer->dataProvider()->fields(), 1 );
|
||||
f1.setGeometry( geometry );
|
||||
f1.setAttributes( QgsAttributes() << 1 << QDate( 2020, 1, 1 ) );
|
||||
QgsFeature f2( tempLayer->dataProvider()->fields(), 2 );
|
||||
f2.setGeometry( geometry );
|
||||
f2.setAttributes( QgsAttributes() << 2 << QDate( 2020, 3, 1 ) );
|
||||
QgsFeature f3( tempLayer->dataProvider()->fields(), 3 );
|
||||
line.clear();
|
||||
line << QgsPointXY( -3, -3 ) << QgsPointXY( -2, -2 );
|
||||
geometry = QgsGeometry::fromPolylineXY( line );
|
||||
f3.setGeometry( geometry );
|
||||
f3.setAttributes( QgsAttributes() << 3 << QDate( 2020, 1, 1 ) );
|
||||
QVERIFY( tempLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 << f2 << f3 ) );
|
||||
|
||||
const QString filterExpression = QStringLiteral( "col1 >= to_date('2020-02-03')" );
|
||||
tempLayer->setConstraintExpression( 1, QStringLiteral( "col1 >= to_date('2020-02-03')" ) );
|
||||
tempLayer->setFieldConstraint( 1, QgsFieldConstraints::ConstraintExpression, QgsFieldConstraints::ConstraintStrengthHard );
|
||||
|
||||
std::unique_ptr< QgsAttributeTableDialog > dlg( new QgsAttributeTableDialog( tempLayer.get(),
|
||||
QgsAttributeTableFilterModel::ShowAll,
|
||||
nullptr,
|
||||
Qt::Window,
|
||||
nullptr,
|
||||
filterExpression ) );
|
||||
dlg->mFeatureFilterWidget->filterInvalid();
|
||||
|
||||
// feature id 2 is filtered out due not matching the provided filter expression
|
||||
QCOMPARE( dlg->mMainView->filteredFeatures(), QgsFeatureIds() << 1 << 3 );
|
||||
}
|
||||
|
||||
QGSTEST_MAIN( TestQgsAttributeTable )
|
||||
#include "testqgsattributetable.moc"
|
||||
|
||||
@ -264,7 +264,9 @@ class TestQgsVectorLayerUtils(unittest.TestCase):
|
||||
layer = createLayerWithOnePoint()
|
||||
|
||||
# field expression check
|
||||
self.assertFalse(QgsVectorLayerUtils.attributeHasConstraints(layer, 1))
|
||||
layer.setConstraintExpression(1, 'fldint>5')
|
||||
self.assertTrue(QgsVectorLayerUtils.attributeHasConstraints(layer, 1))
|
||||
|
||||
f = QgsFeature(2)
|
||||
f.setAttributes(["test123", 6])
|
||||
@ -297,7 +299,10 @@ class TestQgsVectorLayerUtils(unittest.TestCase):
|
||||
self.assertTrue(res)
|
||||
self.assertEqual(len(errors), 0)
|
||||
|
||||
self.assertFalse(QgsVectorLayerUtils.attributeHasConstraints(layer, 1))
|
||||
layer.setFieldConstraint(1, QgsFieldConstraints.ConstraintNotNull)
|
||||
self.assertTrue(QgsVectorLayerUtils.attributeHasConstraints(layer, 1))
|
||||
|
||||
res, errors = QgsVectorLayerUtils.validateAttribute(layer, f, 1)
|
||||
self.assertFalse(res)
|
||||
self.assertEqual(len(errors), 1)
|
||||
@ -315,7 +320,11 @@ class TestQgsVectorLayerUtils(unittest.TestCase):
|
||||
res, errors = QgsVectorLayerUtils.validateAttribute(layer, f, 1)
|
||||
self.assertTrue(res)
|
||||
self.assertEqual(len(errors), 0)
|
||||
|
||||
self.assertFalse(QgsVectorLayerUtils.attributeHasConstraints(layer, 1))
|
||||
layer.setFieldConstraint(1, QgsFieldConstraints.ConstraintUnique)
|
||||
self.assertTrue(QgsVectorLayerUtils.attributeHasConstraints(layer, 1))
|
||||
|
||||
res, errors = QgsVectorLayerUtils.validateAttribute(layer, f, 1)
|
||||
self.assertFalse(res)
|
||||
self.assertEqual(len(errors), 1)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user