Skip to content
Permalink
Browse files

[FEATURE][layouts] Allow layout attribute tables to be styled using t…

…he foreground

and background colors of matching conditional styles attached to the layer

When the new "Apply layer conditional styling colors" option is enabled in the
layout attribute table settings, any conditional styling rules present in the
layer will be applied inside the layout attribute table (foreground and
background colors only, for now!).

Refs #25712

Sponsored by City of Canning
  • Loading branch information
nyalldawson committed Jan 7, 2020
1 parent 9a5855a commit 400e3dace1cabc7656bc57678ec81df2053e76d7
@@ -290,6 +290,9 @@ be replaced by a line break.
%End


virtual QgsConditionalStyle conditionalCellStyle( int row, int column ) const;


virtual QgsExpressionContext createExpressionContext() const;

virtual void finalizeRestoreFromXml();
@@ -298,6 +301,26 @@ be replaced by a line break.
virtual void refreshDataDefinedProperty( QgsLayoutObject::DataDefinedProperty property = QgsLayoutObject::AllProperties );


bool useConditionalStyling() const;
%Docstring
Returns ``True`` if the attribute table will be rendered using the conditional styling
properties of the linked vector layer.

.. seealso:: :py:func:`setUseConditionalStyling`

.. versionadded:: 3.12
%End

void setUseConditionalStyling( bool enabled );
%Docstring
Sets whether the attribute table will be rendered using the conditional styling
properties of the linked vector layer.

.. seealso:: :py:func:`useConditionalStyling`

.. versionadded:: 3.12
%End

protected:

virtual bool writePropertiesToElement( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const;
@@ -497,6 +497,13 @@ Fetches the contents used for the cells in the table.
:return: ``True`` if table contents were successfully retrieved.

:param contents: QgsLayoutTableContents to store retrieved row data in
%End

virtual QgsConditionalStyle conditionalCellStyle( int row, int column ) const;
%Docstring
Returns the conditional style to use for the cell at ``row``, ``column``.

.. versionadded:: 3.12
%End

QgsLayoutTableContents &contents();
@@ -69,6 +69,7 @@ QgsLayoutAttributeTableWidget::QgsLayoutAttributeTableWidget( QgsLayoutFrame *fr
connect( mHideEmptyBgCheckBox, &QCheckBox::toggled, this, &QgsLayoutAttributeTableWidget::mHideEmptyBgCheckBox_toggled );
connect( mWrapBehaviorComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsLayoutAttributeTableWidget::mWrapBehaviorComboBox_currentIndexChanged );
connect( mAdvancedCustomizationButton, &QPushButton::clicked, this, &QgsLayoutAttributeTableWidget::mAdvancedCustomizationButton_clicked );
connect( mUseConditionalStylingCheckBox, &QCheckBox::stateChanged, this, &QgsLayoutAttributeTableWidget::useConditionalStylingChanged );
setPanelTitle( tr( "Table Properties" ) );

mContentFontToolButton->setMode( QgsFontButton::ModeQFont );
@@ -476,6 +477,7 @@ void QgsLayoutAttributeTableWidget::updateGuiElements()
mFeatureFilterCheckBox->setCheckState( mTable->filterFeatures() ? Qt::Checked : Qt::Unchecked );
mFeatureFilterEdit->setEnabled( mTable->filterFeatures() );
mFeatureFilterButton->setEnabled( mTable->filterFeatures() );
mUseConditionalStylingCheckBox->setChecked( mTable->useConditionalStyling() );

mHeaderHAlignmentComboBox->setCurrentIndex( static_cast<int>( mTable->headerHAlignment() ) );
mHeaderModeComboBox->setCurrentIndex( static_cast<int>( mTable->headerMode() ) );
@@ -914,6 +916,19 @@ void QgsLayoutAttributeTableWidget::mAdvancedCustomizationButton_clicked()
d.exec();
}

void QgsLayoutAttributeTableWidget::useConditionalStylingChanged( bool checked )
{
if ( !mTable )
{
return;
}

mTable->beginCommand( tr( "Toggle Table Conditional Styling" ) );
mTable->setUseConditionalStyling( checked );
mTable->update();
mTable->endCommand();
}

void QgsLayoutAttributeTableWidget::mDrawEmptyCheckBox_toggled( bool checked )
{
if ( !mTable )
@@ -86,6 +86,7 @@ class QgsLayoutAttributeTableWidget: public QgsLayoutItemBaseWidget, private Ui:
void mHideEmptyBgCheckBox_toggled( bool checked );
void mWrapBehaviorComboBox_currentIndexChanged( int index );
void mAdvancedCustomizationButton_clicked();
void useConditionalStylingChanged( bool checked );

//! Inserts a new maximum number of features into the spin box (without the spinbox emitting a signal)
void setMaximumNumberOfFeatures( int n );
@@ -30,6 +30,7 @@
#include "qgsmapsettings.h"
#include "qgsexpressioncontextutils.h"
#include "qgsgeometryengine.h"
#include "qgsconditionalstyle.h"

//QgsLayoutAttributeTableCompare

@@ -46,10 +47,10 @@ class CORE_EXPORT QgsLayoutAttributeTableCompare
* Constructor for QgsLayoutAttributeTableCompare.
*/
QgsLayoutAttributeTableCompare() = default;
bool operator()( const QgsLayoutTableRow &m1, const QgsLayoutTableRow &m2 )
bool operator()( const QVector< QPair< QVariant, QgsConditionalStyle > > &m1, const QVector< QPair< QVariant, QgsConditionalStyle > > &m2 )
{
return ( mAscending ? qgsVariantLessThan( m1[mCurrentSortColumn], m2[mCurrentSortColumn] )
: qgsVariantGreaterThan( m1[mCurrentSortColumn], m2[mCurrentSortColumn] ) );
return ( mAscending ? qgsVariantLessThan( m1[mCurrentSortColumn].first, m2[mCurrentSortColumn].first )
: qgsVariantGreaterThan( m1[mCurrentSortColumn].first, m2[mCurrentSortColumn].first ) );
}

/**
@@ -238,6 +239,23 @@ void QgsLayoutItemAttributeTable::disconnectCurrentMap()
mMap = nullptr;
}

bool QgsLayoutItemAttributeTable::useConditionalStyling() const
{
return mUseConditionalStyling;
}

void QgsLayoutItemAttributeTable::setUseConditionalStyling( bool useConditionalStyling )
{
if ( useConditionalStyling == mUseConditionalStyling )
{
return;
}

mUseConditionalStyling = useConditionalStyling;
refreshAttributes();
emit changed();
}

void QgsLayoutItemAttributeTable::setMap( QgsLayoutItemMap *map )
{
if ( map == mMap )
@@ -414,6 +432,8 @@ bool QgsLayoutItemAttributeTable::getTableContents( QgsLayoutTableContents &cont
return false;
}

const QgsConditionalLayerStyles *conditionalStyles = layer->conditionalStyles();

QgsExpressionContext context = createExpressionContext();
context.setFields( layer->fields() );

@@ -503,6 +523,11 @@ bool QgsLayoutItemAttributeTable::getTableContents( QgsLayoutTableContents &cont
int counter = 0;
QgsFeatureIterator fit = layer->getFeatures( req );

mConditionalStyles.clear();

QVector< QVector< QPair< QVariant, QgsConditionalStyle > > > tempContents;
QgsLayoutTableContents existingContents;

while ( fit.nextFeature( f ) && counter < mMaximumNumberOfFeatures )
{
context.setFeature( f );
@@ -539,15 +564,45 @@ bool QgsLayoutItemAttributeTable::getTableContents( QgsLayoutTableContents &cont
continue;
}

QgsLayoutTableRow currentRow;
QgsConditionalStyle rowStyle;

if ( mUseConditionalStyling )
{
const QList<QgsConditionalStyle> styles = QgsConditionalStyle::matchingConditionalStyles( conditionalStyles->rowStyles(), QVariant(), context );
rowStyle = QgsConditionalStyle::compressStyles( styles );
}

// We need to build up two different lists here -- one is a pair of the cell contents along with the cell style.
// We need this one because we do a sorting step later, and we need to ensure that the cell styling is attached to the right row and sorted
// correctly when this occurs
// We also need a list of just the cell contents, so that we can do a quick check for row uniqueness (when the
// corresponding option is enabled)
QVector< QPair< QVariant, QgsConditionalStyle > > currentRow;
currentRow.reserve( mColumns.count() );
QgsLayoutTableRow rowContents;
rowContents.reserve( mColumns.count() );

for ( QgsLayoutTableColumn *column : qgis::as_const( mColumns ) )
{
int idx = layer->fields().lookupField( column->attribute() );

QgsConditionalStyle style;

if ( idx != -1 )
{
currentRow << replaceWrapChar( f.attributes().at( idx ) );
const QVariant val = f.attributes().at( idx );

if ( mUseConditionalStyling )
{
QList<QgsConditionalStyle> styles = conditionalStyles->fieldStyles( layer->fields().at( idx ).name() );
styles = QgsConditionalStyle::matchingConditionalStyles( styles, val, context );
styles.insert( 0, rowStyle );
style = QgsConditionalStyle::compressStyles( styles );
}

QVariant v = replaceWrapChar( val );
currentRow << qMakePair( v, style );
rowContents << v;
}
else
{
@@ -556,15 +611,21 @@ bool QgsLayoutItemAttributeTable::getTableContents( QgsLayoutTableContents &cont
context.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "row_number" ), counter + 1, true ) );
expression->prepare( &context );
QVariant value = expression->evaluate( &context );
currentRow << value;

currentRow << qMakePair( value, rowStyle );
rowContents << value;
}
}

if ( !mShowUniqueRowsOnly || !contentsContainsRow( contents, currentRow ) )
if ( mShowUniqueRowsOnly )
{
contents << currentRow;
++counter;
if ( contentsContainsRow( existingContents, rowContents ) )
continue;
}

tempContents << currentRow;
existingContents << rowContents;
++counter;
}

//sort the list, starting with the last attribute
@@ -574,13 +635,40 @@ bool QgsLayoutItemAttributeTable::getTableContents( QgsLayoutTableContents &cont
{
c.setSortColumn( sortColumns.at( i ).first );
c.setAscending( sortColumns.at( i ).second );
std::stable_sort( contents.begin(), contents.end(), c );
std::stable_sort( tempContents.begin(), tempContents.end(), c );
}

// build final table contents
contents.reserve( tempContents.size() );
mConditionalStyles.reserve( tempContents.size() );
for ( auto it = tempContents.constBegin(); it != tempContents.constEnd(); ++it )
{
QgsLayoutTableRow row;
QList< QgsConditionalStyle > rowStyles;
row.reserve( it->size() );
rowStyles.reserve( it->size() );

for ( auto cellIt = it->constBegin(); cellIt != it->constEnd(); ++cellIt )
{
row << cellIt->first;
rowStyles << cellIt->second;
}
contents << row;
mConditionalStyles << rowStyles;
}

recalculateTableSize();
return true;
}

QgsConditionalStyle QgsLayoutItemAttributeTable::conditionalCellStyle( int row, int column ) const
{
if ( row >= mConditionalStyles.size() )
return QgsConditionalStyle();

return mConditionalStyles.at( row ).at( column );
}

QgsExpressionContext QgsLayoutItemAttributeTable::createExpressionContext() const
{
QgsExpressionContext context = QgsLayoutTable::createExpressionContext();
@@ -736,6 +824,7 @@ bool QgsLayoutItemAttributeTable::writePropertiesToElement( QDomElement &tableEl
tableElem.setAttribute( QStringLiteral( "filterFeatures" ), mFilterFeatures ? QStringLiteral( "true" ) : QStringLiteral( "false" ) );
tableElem.setAttribute( QStringLiteral( "featureFilter" ), mFeatureFilter );
tableElem.setAttribute( QStringLiteral( "wrapString" ), mWrapString );
tableElem.setAttribute( QStringLiteral( "useConditionalStyling" ), mUseConditionalStyling );

if ( mMap )
{
@@ -778,6 +867,7 @@ bool QgsLayoutItemAttributeTable::readPropertiesFromElement( const QDomElement &
mFeatureFilter = itemElem.attribute( QStringLiteral( "featureFilter" ), QString() );
mMaximumNumberOfFeatures = itemElem.attribute( QStringLiteral( "maxFeatures" ), QStringLiteral( "5" ) ).toInt();
mWrapString = itemElem.attribute( QStringLiteral( "wrapString" ) );
mUseConditionalStyling = itemElem.attribute( QStringLiteral( "useConditionalStyling" ), QStringLiteral( "0" ) ).toInt();

//map
mMapUuid = itemElem.attribute( QStringLiteral( "mapUuid" ) );
@@ -288,11 +288,31 @@ class CORE_EXPORT QgsLayoutItemAttributeTable: public QgsLayoutTable
*/
bool getTableContents( QgsLayoutTableContents &contents ) override SIP_SKIP;

QgsConditionalStyle conditionalCellStyle( int row, int column ) const override;

QgsExpressionContext createExpressionContext() const override;
void finalizeRestoreFromXml() override;

void refreshDataDefinedProperty( QgsLayoutObject::DataDefinedProperty property = QgsLayoutObject::AllProperties ) override;

/**
* Returns TRUE if the attribute table will be rendered using the conditional styling
* properties of the linked vector layer.
*
* \see setUseConditionalStyling()
* \since QGIS 3.12
*/
bool useConditionalStyling() const;

/**
* Sets whether the attribute table will be rendered using the conditional styling
* properties of the linked vector layer.
*
* \see useConditionalStyling()
* \since QGIS 3.12
*/
void setUseConditionalStyling( bool enabled );

protected:

bool writePropertiesToElement( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const override;
@@ -342,6 +362,10 @@ class CORE_EXPORT QgsLayoutItemAttributeTable: public QgsLayoutTable

QString mWrapString;

bool mUseConditionalStyling = false;

QList< QList< QgsConditionalStyle > > mConditionalStyles;

/**
* Returns a list of attribute indices corresponding to displayed fields in the table.
* \note kept for compatibility with 2.0 api only
@@ -435,7 +435,13 @@ void QgsLayoutTable::render( QgsLayoutItemRenderContext &context, const QRectF &
p->save();
p->setClipRect( fullCell );
const QRectF textCell = QRectF( currentX, currentY + mCellMargin, mMaxColumnWidthMap[col], rowHeight - 2 * mCellMargin );
QgsLayoutUtils::drawText( p, textCell, str, mContentFont, mContentFontColor, column->hAlignment(), column->vAlignment(), textFlag );

const QgsConditionalStyle style = conditionalCellStyle( row, col );
QColor foreColor = mContentFontColor;
if ( style.textColor().isValid() )
foreColor = style.textColor();

QgsLayoutUtils::drawText( p, textCell, str, mContentFont, foreColor, column->hAlignment(), column->vAlignment(), textFlag );
p->restore();

currentX += mMaxColumnWidthMap[ col ];
@@ -789,6 +795,11 @@ QMap<int, QString> QgsLayoutTable::headerLabels() const
return headers;
}

QgsConditionalStyle QgsLayoutTable::conditionalCellStyle( int, int ) const
{
return QgsConditionalStyle();
}

QSizeF QgsLayoutTable::fixedFrameSize( const int frameIndex ) const
{
Q_UNUSED( frameIndex )
@@ -1218,6 +1229,13 @@ QColor QgsLayoutTable::backgroundColor( int row, int column ) const
if ( style->enabled && row == mTableContents.count() - 1 )
color = style->cellBackgroundColor;

if ( row >= 0 )
{
QgsConditionalStyle conditionalStyle = conditionalCellStyle( row, column );
if ( conditionalStyle.backgroundColor().isValid() )
color = conditionalStyle.backgroundColor();
}

return color;
}

@@ -21,6 +21,7 @@
#include "qgis_core.h"
#include "qgis_sip.h"
#include "qgslayoutmultiframe.h"
#include "qgsconditionalstyle.h"
#include <QFont>
#include <QColor>
#include <QPair>
@@ -465,6 +466,13 @@ class CORE_EXPORT QgsLayoutTable: public QgsLayoutMultiFrame
*/
virtual bool getTableContents( QgsLayoutTableContents &contents ) = 0;

/**
* Returns the conditional style to use for the cell at \a row, \a column.
*
* \since QGIS 3.12
*/
virtual QgsConditionalStyle conditionalCellStyle( int row, int column ) const;

/**
* Returns the current contents of the table. Excludes header cells.
*/

0 comments on commit 400e3da

Please sign in to comment.
You can’t perform that action at this time.