Skip to content

Commit

Permalink
[attributes table] Add constraint-based conditional styling
Browse files Browse the repository at this point in the history
  • Loading branch information
nirvn committed Jan 13, 2023
1 parent cd5eddf commit 4c8804e
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 32 deletions.
24 changes: 24 additions & 0 deletions python/core/auto_generated/qgsconditionalstyle.sip.in
Expand Up @@ -43,12 +43,36 @@ Each row will check be checked against each rule.
.. seealso:: :py:func:`rowStyles`

.. versionadded:: 2.12
%End

QgsConditionalStyles constraintStyles() const;
%Docstring
Returns a list of row styles associated with layer constraints.

.. seealso:: :py:func:`setConstraintStyles`

.. versionadded:: 3.30
%End

void setConstraintStyles( const QgsConditionalStyles &styles );
%Docstring
Sets the conditional ``styles`` for fields constraint.

The field name is inserted into a @value variable to conduct
expressionchecks.

.. seealso:: :py:func:`constraintStyles`

.. 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

Expand Down
9 changes: 8 additions & 1 deletion src/core/layout/qgslayoutitemattributetable.cpp
Expand Up @@ -575,9 +575,16 @@ bool QgsLayoutItemAttributeTable::getTableContents( QgsLayoutTableContents &cont

if ( mUseConditionalStyling )
{
QList<QgsConditionalStyle> styles = conditionalStyles->fieldStyles( layer->fields().at( idx ).name() );
const QString fieldName = layer->fields().at( idx ).name();

QList<QgsConditionalStyle> styles = conditionalStyles->constraintStyles();
styles = QgsConditionalStyle::matchingConditionalStyles( styles, fieldName, context );
const QgsConditionalStyle constraintStyle = QgsConditionalStyle::compressStyles( styles );

styles = conditionalStyles->fieldStyles( fieldName );
styles = QgsConditionalStyle::matchingConditionalStyles( styles, val, context );
styles.insert( 0, rowStyle );
styles.insert( 0, constraintStyle );
style = QgsConditionalStyle::compressStyles( styles );
}

Expand Down
45 changes: 40 additions & 5 deletions src/core/qgsconditionalstyle.cpp
Expand Up @@ -23,7 +23,22 @@

QgsConditionalLayerStyles::QgsConditionalLayerStyles( QObject *parent )
: QObject( parent )
{}
{
}

QgsConditionalStyles QgsConditionalLayerStyles::constraintStyles() const
{
return mConstraintStyles;
}

void QgsConditionalLayerStyles::setConstraintStyles( const QgsConditionalStyles &styles )
{
if ( styles == mConstraintStyles )
return;

mConstraintStyles = styles;
emit changed();
}

QgsConditionalStyles QgsConditionalLayerStyles::rowStyles() const
{
Expand Down Expand Up @@ -56,13 +71,21 @@ QList<QgsConditionalStyle> QgsConditionalLayerStyles::fieldStyles( const QString
bool QgsConditionalLayerStyles::writeXml( QDomNode &node, QDomDocument &doc, const QgsReadWriteContext &context ) const
{
QDomElement stylesel = doc.createElement( QStringLiteral( "conditionalstyles" ) );

QDomElement constraintel = doc.createElement( QStringLiteral( "constraintstyles" ) );
const auto constMConstraintStyles = mConstraintStyles;
for ( const QgsConditionalStyle &style : constMConstraintStyles )
{
style.writeXml( constraintel, doc, context );
}
stylesel.appendChild( constraintel );

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" ) );
Expand All @@ -79,7 +102,6 @@ bool QgsConditionalLayerStyles::writeXml( QDomNode &node, QDomDocument &doc, con
}
fieldsel.appendChild( fieldel );
}

stylesel.appendChild( fieldsel );

node.appendChild( stylesel );
Expand All @@ -100,11 +122,24 @@ bool QgsConditionalLayerStyles::rulesNeedGeometry() const

bool QgsConditionalLayerStyles::readXml( const QDomNode &node, const QgsReadWriteContext &context )
{
const QDomElement condel = node.firstChildElement( QStringLiteral( "conditionalstyles" ) );
mConstraintStyles.clear();
mRowStyles.clear();
mFieldStyles.clear();

const QDomElement condel = node.firstChildElement( QStringLiteral( "conditionalstyles" ) );

const QDomElement constraintstylesel = condel.firstChildElement( QStringLiteral( "constraintstyles" ) );
QDomNodeList nodelist = constraintstylesel.toElement().elementsByTagName( QStringLiteral( "style" ) );
for ( int i = 0; i < nodelist.count(); i++ )
{
const QDomElement styleElm = nodelist.at( i ).toElement();
QgsConditionalStyle style = QgsConditionalStyle();
style.readXml( styleElm, context );
mConstraintStyles.append( style );
}

const QDomElement rowstylesel = condel.firstChildElement( QStringLiteral( "rowstyles" ) );
QDomNodeList nodelist = rowstylesel.toElement().elementsByTagName( QStringLiteral( "style" ) );
nodelist = rowstylesel.toElement().elementsByTagName( QStringLiteral( "style" ) );
for ( int i = 0; i < nodelist.count(); i++ )
{
const QDomElement styleElm = nodelist.at( i ).toElement();
Expand Down
23 changes: 23 additions & 0 deletions src/core/qgsconditionalstyle.h
Expand Up @@ -65,9 +65,31 @@ class CORE_EXPORT QgsConditionalLayerStyles : public QObject
*/
void setRowStyles( const QgsConditionalStyles &styles );

/**
* Returns a list of row styles associated with layer constraints.
*
* \see setConstraintStyles()
* \since QGIS 3.30
*/
QgsConditionalStyles constraintStyles() const;

/**
* Sets the conditional \a styles for fields constraint.
*
* The field name is inserted into a @value variable to conduct
* expressionchecks.
*
* \see constraintStyles()
* \since QGIS 3.30
*/
void setConstraintStyles( const QgsConditionalStyles &styles );

/**
* 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 );
Expand Down Expand Up @@ -111,6 +133,7 @@ class CORE_EXPORT QgsConditionalLayerStyles : public QObject
private:
QHash<QString, QgsConditionalStyles> mFieldStyles;
QgsConditionalStyles mRowStyles;
QgsConditionalStyles mConstraintStyles;
};

/**
Expand Down
6 changes: 5 additions & 1 deletion src/gui/attributetable/qgsattributetablemodel.cpp
Expand Up @@ -770,11 +770,15 @@ 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 );

styles = QgsConditionalStyle::matchingConditionalStyles( mLayer->conditionalStyles()->constraintStyles(), field.name(), mExpressionContext );
const QgsConditionalStyle constraintstyle = QgsConditionalStyle::compressStyles( styles );

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() )
Expand Down
54 changes: 50 additions & 4 deletions src/gui/attributetable/qgsfieldconditionalformatwidget.cpp
Expand Up @@ -34,8 +34,9 @@ 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( constraintRadio, &QAbstractButton::clicked, this, &QgsFieldConditionalFormatWidget::typeChanged );
connect( mNewButton, &QAbstractButton::clicked, this, &QgsFieldConditionalFormatWidget::addNewRule );
connect( listView, &QAbstractItemView::clicked, this, &QgsFieldConditionalFormatWidget::ruleClicked );
mModel = new QStandardItemModel( listView );
Expand Down Expand Up @@ -142,16 +143,47 @@ QList<QgsConditionalStyle> QgsFieldConditionalFormatWidget::getStyles()
{
styles = mLayer->conditionalStyles()->fieldStyles( mFieldCombo->currentField() );
}
if ( rowRadio->isChecked() )
else if ( rowRadio->isChecked() )
{
styles = mLayer->conditionalStyles()->rowStyles();
}
else if ( constraintRadio->isChecked() )
{
styles = mLayer->conditionalStyles()->constraintStyles();
}

return styles;
}

void QgsFieldConditionalFormatWidget::addNewRule()
{
editStyle( -1, QgsConditionalStyle() );
if ( constraintRadio->isChecked() )
{
// Add default constraint styles
QgsConditionalStyles styles;

QgsConditionalStyle invalidHardConstraint;
invalidHardConstraint.setName( tr( "Hard constraint failure" ) );
invalidHardConstraint.setRule( QStringLiteral( "is_attribute_valid(@value, strength:='hard') = false" ) );
invalidHardConstraint.setBackgroundColor( QColor( 255, 152, 0 ) );
invalidHardConstraint.setTextColor( QColor( 0, 0, 0 ) );
styles << invalidHardConstraint;

QgsConditionalStyle invalidSoftConstraint;
invalidHardConstraint.setName( tr( "Soft constraint failure" ) );
invalidSoftConstraint.setRule( QStringLiteral( "is_attribute_valid(@value, strength:='hard') = true and is_attribute_valid(@value, strength:='soft') = false" ) );
invalidSoftConstraint.setBackgroundColor( QColor( 255, 191, 12 ) );
invalidSoftConstraint.setTextColor( QColor( 0, 0, 0 ) );
styles << invalidSoftConstraint;

mLayer->conditionalStyles()->setConstraintStyles( styles );

reloadStyles();
}
else
{
editStyle( -1, QgsConditionalStyle() );
}
}

void QgsFieldConditionalFormatWidget::reset()
Expand Down Expand Up @@ -187,6 +219,20 @@ QList<QgsConditionalStyle> QgsFieldConditionalFormatWidget::defaultPresets()
return styles;
}

void QgsFieldConditionalFormatWidget::typeChanged()
{
if ( constraintRadio->isChecked() )
{
mNewButton->setText( tr( "Add Constraint Rules" ) );
}
else
{
mNewButton->setText( tr( "New Rule" ) );
}

reloadStyles();
}

void QgsFieldConditionalFormatWidget::reloadStyles()
{
mModel->clear();
Expand Down
1 change: 1 addition & 0 deletions src/gui/attributetable/qgsfieldconditionalformatwidget.h
Expand Up @@ -110,6 +110,7 @@ class GUI_EXPORT QgsFieldConditionalFormatWidget : public QgsPanelWidget, privat

private slots:

void typeChanged();
void ruleClicked( const QModelIndex &index );
void reloadStyles();
void addNewRule();
Expand Down
56 changes: 35 additions & 21 deletions src/ui/qgsfieldconditionalformatwidget.ui
Expand Up @@ -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="3" 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>
Expand All @@ -45,42 +42,59 @@
</property>
</widget>
</item>
<item row="0" column="2">
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="topMargin">
<number>0</number>
</property>
<item>
<widget class="QgsFieldComboBox" name="mFieldCombo"/>
<widget class="QRadioButton" name="fieldRadio">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Field</string>
</property>
<property name="checked">
<bool>true</bool>
</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="0" column="0">
<widget class="QRadioButton" name="fieldRadio">
<item row="1" column="0">
<widget class="QRadioButton" name="rowRadio">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Field</string>
</property>
<property name="checked">
<bool>true</bool>
<string>Full row</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QRadioButton" name="rowRadio">
<item row="2" column="0">
<widget class="QRadioButton" name="constraintRadio">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Full row</string>
<string>Constraints</string>
</property>
</widget>
</item>
Expand Down

0 comments on commit 4c8804e

Please sign in to comment.