diff --git a/python/core/layout/qgslayoutframe.sip b/python/core/layout/qgslayoutframe.sip index 2b60d1fac88..dea3a4e2966 100644 --- a/python/core/layout/qgslayoutframe.sip +++ b/python/core/layout/qgslayoutframe.sip @@ -56,6 +56,11 @@ class QgsLayoutFrame: QgsLayoutItem :rtype: QgsLayoutMultiFrame %End + virtual QgsLayoutSize minimumSize() const; + + virtual QgsLayoutSize fixedSize() const; + + QRectF extent() const; %Docstring diff --git a/python/core/layout/qgslayoutitem.sip b/python/core/layout/qgslayoutitem.sip index ea914223f3a..154fa389647 100644 --- a/python/core/layout/qgslayoutitem.sip +++ b/python/core/layout/qgslayoutitem.sip @@ -276,7 +276,7 @@ class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem, QgsLayoutUndoObjectInt :rtype: ReferencePoint %End - QgsLayoutSize fixedSize() const; + virtual QgsLayoutSize fixedSize() const; %Docstring Returns the fixed size of the item, if applicable, or an empty size if item can be freely resized. diff --git a/python/core/layout/qgslayoutitemattributetable.sip b/python/core/layout/qgslayoutitemattributetable.sip index 8088e3a0e59..aa2dcc03c17 100644 --- a/python/core/layout/qgslayoutitemattributetable.sip +++ b/python/core/layout/qgslayoutitemattributetable.sip @@ -29,9 +29,11 @@ class QgsLayoutItemAttributeTable: QgsLayoutTable RelationChildren }; - QgsLayoutItemAttributeTable( QgsLayout *layout ); + QgsLayoutItemAttributeTable( QgsLayout *layout /TransferThis/ ); %Docstring Constructor for QgsLayoutItemAttributeTable, attached to the specified ``layout``. + + Ownership is transferred to the layout. %End virtual int type() const; diff --git a/python/core/layout/qgslayoutitemhtml.sip b/python/core/layout/qgslayoutitemhtml.sip index 4bb98c15b95..71f4df6acdd 100644 --- a/python/core/layout/qgslayoutitemhtml.sip +++ b/python/core/layout/qgslayoutitemhtml.sip @@ -27,9 +27,11 @@ class QgsLayoutItemHtml: QgsLayoutMultiFrame ManualHtml }; - QgsLayoutItemHtml( QgsLayout *layout ); + QgsLayoutItemHtml( QgsLayout *layout /TransferThis/ ); %Docstring Constructor for QgsLayoutItemHtml, with the specified parent ``layout``. + + Ownership is transferred to the layout. %End ~QgsLayoutItemHtml(); diff --git a/python/core/layout/qgslayoutitemtexttable.sip b/python/core/layout/qgslayoutitemtexttable.sip index 2ee363663c4..439e35a3b59 100644 --- a/python/core/layout/qgslayoutitemtexttable.sip +++ b/python/core/layout/qgslayoutitemtexttable.sip @@ -21,9 +21,11 @@ class QgsLayoutItemTextTable : QgsLayoutTable %End public: - QgsLayoutItemTextTable( QgsLayout *layout ); + QgsLayoutItemTextTable( QgsLayout *layout /TransferThis/ ); %Docstring Constructor for QgsLayoutItemTextTable, for the specified ``layout``. + + Ownership is transferred to the layout. %End virtual int type() const; diff --git a/src/core/layout/qgslayoutframe.cpp b/src/core/layout/qgslayoutframe.cpp index deaa17c101e..88358d75f67 100644 --- a/src/core/layout/qgslayoutframe.cpp +++ b/src/core/layout/qgslayoutframe.cpp @@ -35,10 +35,8 @@ QgsLayoutFrame::QgsLayoutFrame( QgsLayout *layout, QgsLayoutMultiFrame *multiFra update(); } ); -#if 0 //TODO //force recalculation of rect, so that multiframe specified sizes can be applied - setSceneRect( QRectF( pos().x(), pos().y(), rect().width(), rect().height() ) ); -#endif + refreshItemSize(); } } @@ -51,6 +49,29 @@ QgsLayoutMultiFrame *QgsLayoutFrame::multiFrame() const { return mMultiFrame; } + +QgsLayoutSize QgsLayoutFrame::minimumSize() const +{ + if ( !mMultiFrame ) + return QgsLayoutSize(); + + //calculate index of frame + int frameIndex = mMultiFrame->frameIndex( const_cast< QgsLayoutFrame * >( this ) ); + //check minimum size + return QgsLayoutSize( mMultiFrame->minFrameSize( frameIndex ), QgsUnitTypes::LayoutMillimeters ); +} + +QgsLayoutSize QgsLayoutFrame::fixedSize() const +{ + if ( !mMultiFrame ) + return QgsLayoutSize(); + + //calculate index of frame + int frameIndex = mMultiFrame->frameIndex( const_cast< QgsLayoutFrame * >( this ) ); + //check fixed size + return QgsLayoutSize( mMultiFrame->fixedFrameSize( frameIndex ), QgsUnitTypes::LayoutMillimeters ); +} + #if 0// TODO - save/restore multiframe uuid! bool QgsLayoutFrame::writeXml( QDomElement &elem, QDomDocument &doc ) const { @@ -168,37 +189,6 @@ QString QgsLayoutFrame::displayName() const return tr( "" ); } -#if 0 //TODO -void QgsLayoutFrame::setSceneRect( const QRectF &rectangle ) -{ - QRectF fixedRect = rectangle; - - if ( mMultiFrame ) - { - //calculate index of frame - int frameIndex = mMultiFrame->frameIndex( this ); - - QSizeF fixedSize = mMultiFrame->fixedFrameSize( frameIndex ); - if ( fixedSize.width() > 0 ) - { - fixedRect.setWidth( fixedSize.width() ); - } - if ( fixedSize.height() > 0 ) - { - fixedRect.setHeight( fixedSize.height() ); - } - - //check minimum size - QSizeF minSize = mMultiFrame->minFrameSize( frameIndex ); - fixedRect.setWidth( std::max( minSize.width(), fixedRect.width() ) ); - fixedRect.setHeight( std::max( minSize.height(), fixedRect.height() ) ); - } - - QgsComposerItem::setSceneRect( fixedRect ); -} -#endif - - void QgsLayoutFrame::draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle ) { if ( mMultiFrame ) diff --git a/src/core/layout/qgslayoutframe.h b/src/core/layout/qgslayoutframe.h index 59fd475e5e0..1b589157169 100644 --- a/src/core/layout/qgslayoutframe.h +++ b/src/core/layout/qgslayoutframe.h @@ -64,11 +64,10 @@ class CORE_EXPORT QgsLayoutFrame: public QgsLayoutItem */ QgsLayoutMultiFrame *multiFrame() const; -#if 0 //TODO - //Overridden to handle fixed frame sizes set by multi frame - void setSceneRect( const QRectF &rectangle ) override; + QgsLayoutSize minimumSize() const override; + QgsLayoutSize fixedSize() const override; - void paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget ) override; +#if 0 //TODO void beginItemCommand( const QString &text ) override; void endItemCommand() override; bool writeXml( QDomElement &elem, QDomDocument &doc ) const override; diff --git a/src/core/layout/qgslayoutitem.cpp b/src/core/layout/qgslayoutitem.cpp index a9a3e831500..21761a7a012 100644 --- a/src/core/layout/qgslayoutitem.cpp +++ b/src/core/layout/qgslayoutitem.cpp @@ -1200,8 +1200,15 @@ QSizeF QgsLayoutItem::applyFixedSize( const QSizeF &targetSize ) { return targetSize; } + + QSizeF size = targetSize; QSizeF fixedSizeLayoutUnits = mLayout->convertToLayoutUnits( fixedSize() ); - return targetSize.expandedTo( fixedSizeLayoutUnits ); + if ( fixedSizeLayoutUnits.width() > 0 ) + size.setWidth( fixedSizeLayoutUnits.width() ); + if ( fixedSizeLayoutUnits.height() > 0 ) + size.setHeight( fixedSizeLayoutUnits.height() ); + + return size; } void QgsLayoutItem::refreshItemRotation( QPointF *origin ) diff --git a/src/core/layout/qgslayoutitem.h b/src/core/layout/qgslayoutitem.h index 23bcccdbed5..59b4fae0f85 100644 --- a/src/core/layout/qgslayoutitem.h +++ b/src/core/layout/qgslayoutitem.h @@ -299,7 +299,7 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt * \see setFixedSize() * \see minimumSize() */ - QgsLayoutSize fixedSize() const { return mFixedSize; } + virtual QgsLayoutSize fixedSize() const { return mFixedSize; } /** * Returns the minimum allowed size of the item, if applicable, or an empty size if item can be freely diff --git a/src/core/layout/qgslayoutitemattributetable.cpp b/src/core/layout/qgslayoutitemattributetable.cpp index 9a6812cccea..a4f14162f83 100644 --- a/src/core/layout/qgslayoutitemattributetable.cpp +++ b/src/core/layout/qgslayoutitemattributetable.cpp @@ -679,8 +679,11 @@ void QgsLayoutItemAttributeTable::setWrapString( const QString &wrapString ) emit changed(); } -bool QgsLayoutItemAttributeTable::writePropertiesToElement( QDomElement &tableElem, QDomDocument &, const QgsReadWriteContext & ) const +bool QgsLayoutItemAttributeTable::writePropertiesToElement( QDomElement &tableElem, QDomDocument &doc, const QgsReadWriteContext &context ) const { + if ( !QgsLayoutTable::writePropertiesToElement( tableElem, doc, context ) ) + return false; + tableElem.setAttribute( QStringLiteral( "source" ), QString::number( static_cast< int >( mSource ) ) ); tableElem.setAttribute( QStringLiteral( "relationId" ), mRelationId ); tableElem.setAttribute( QStringLiteral( "showUniqueRowsOnly" ), mShowUniqueRowsOnly ); @@ -706,7 +709,7 @@ bool QgsLayoutItemAttributeTable::writePropertiesToElement( QDomElement &tableEl return true; } -bool QgsLayoutItemAttributeTable::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &, const QgsReadWriteContext & ) +bool QgsLayoutItemAttributeTable::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &doc, const QgsReadWriteContext &context ) { QgsVectorLayer *prevLayer = sourceLayer(); if ( prevLayer ) @@ -715,6 +718,9 @@ bool QgsLayoutItemAttributeTable::readPropertiesFromElement( const QDomElement & disconnect( prevLayer, &QgsVectorLayer::layerModified, this, &QgsLayoutTable::refreshAttributes ); } + if ( !QgsLayoutTable::readPropertiesFromElement( itemElem, doc, context ) ) + return false; + mSource = QgsLayoutItemAttributeTable::ContentSource( itemElem.attribute( QStringLiteral( "source" ), QStringLiteral( "0" ) ).toInt() ); mRelationId = itemElem.attribute( QStringLiteral( "relationId" ), QLatin1String( "" ) ); diff --git a/src/core/layout/qgslayoutitemattributetable.h b/src/core/layout/qgslayoutitemattributetable.h index cb388ac26e7..02ff3ef6792 100644 --- a/src/core/layout/qgslayoutitemattributetable.h +++ b/src/core/layout/qgslayoutitemattributetable.h @@ -50,8 +50,10 @@ class CORE_EXPORT QgsLayoutItemAttributeTable: public QgsLayoutTable /** * Constructor for QgsLayoutItemAttributeTable, attached to the specified \a layout. + * + * Ownership is transferred to the layout. */ - QgsLayoutItemAttributeTable( QgsLayout *layout ); + QgsLayoutItemAttributeTable( QgsLayout *layout SIP_TRANSFERTHIS ); int type() const override; QString stringType() const override; diff --git a/src/core/layout/qgslayoutitemhtml.h b/src/core/layout/qgslayoutitemhtml.h index 8cc63bc51e5..696d2a2ef2b 100644 --- a/src/core/layout/qgslayoutitemhtml.h +++ b/src/core/layout/qgslayoutitemhtml.h @@ -48,8 +48,10 @@ class CORE_EXPORT QgsLayoutItemHtml: public QgsLayoutMultiFrame /** * Constructor for QgsLayoutItemHtml, with the specified parent \a layout. + * + * Ownership is transferred to the layout. */ - QgsLayoutItemHtml( QgsLayout *layout ); + QgsLayoutItemHtml( QgsLayout *layout SIP_TRANSFERTHIS ); ~QgsLayoutItemHtml(); diff --git a/src/core/layout/qgslayoutitemtexttable.h b/src/core/layout/qgslayoutitemtexttable.h index 2722b3cabcd..186a842fddc 100644 --- a/src/core/layout/qgslayoutitemtexttable.h +++ b/src/core/layout/qgslayoutitemtexttable.h @@ -36,8 +36,10 @@ class CORE_EXPORT QgsLayoutItemTextTable : public QgsLayoutTable /** * Constructor for QgsLayoutItemTextTable, for the specified \a layout. + * + * Ownership is transferred to the layout. */ - QgsLayoutItemTextTable( QgsLayout *layout ); + QgsLayoutItemTextTable( QgsLayout *layout SIP_TRANSFERTHIS ); int type() const override; QString stringType() const override; diff --git a/src/core/layout/qgslayoutmultiframe.cpp b/src/core/layout/qgslayoutmultiframe.cpp index cf74bf93a76..d1c696f086d 100644 --- a/src/core/layout/qgslayoutmultiframe.cpp +++ b/src/core/layout/qgslayoutmultiframe.cpp @@ -220,7 +220,6 @@ void QgsLayoutMultiFrame::recalculateFrameSizes() void QgsLayoutMultiFrame::recalculateFrameRects() { -#if 0 //TODO if ( mFrameItems.empty() ) { //no frames, nothing to do @@ -230,10 +229,8 @@ void QgsLayoutMultiFrame::recalculateFrameRects() const QList< QgsLayoutFrame * > frames = mFrameItems; for ( QgsLayoutFrame *frame : frames ) { - frame->setSceneRect( QRectF( frame->scenePos().x(), frame->scenePos().y(), - frame->rect().width(), frame->rect().height() ) ); + frame->refreshItemSize(); } -#endif } QgsLayoutFrame *QgsLayoutMultiFrame::createNewFrame( QgsLayoutFrame *currentFrame, QPointF pos, QSizeF size ) diff --git a/tests/src/core/CMakeLists.txt b/tests/src/core/CMakeLists.txt index 2d3be0d0d26..36fe423c438 100755 --- a/tests/src/core/CMakeLists.txt +++ b/tests/src/core/CMakeLists.txt @@ -147,6 +147,7 @@ SET(TESTS testqgslayoutpicture.cpp testqgslayoutscalebar.cpp testqgslayoutshapes.cpp + testqgslayouttable.cpp testqgslayoutunits.cpp testqgslayoututils.cpp testqgslegendrenderer.cpp diff --git a/tests/src/core/testqgslayoutitem.cpp b/tests/src/core/testqgslayoutitem.cpp index c6d8ca9c230..5ce9d773165 100644 --- a/tests/src/core/testqgslayoutitem.cpp +++ b/tests/src/core/testqgslayoutitem.cpp @@ -1074,6 +1074,11 @@ void TestQgsLayoutItem::fixedSize() QGSCOMPARENEAR( item->rect().width(), 2.0 * 25.4, 4 * DBL_EPSILON ); QGSCOMPARENEAR( item->rect().height(), 4.0 * 25.4, 4 * DBL_EPSILON ); + item->attemptResize( QgsLayoutSize( 7.0, 8.0, QgsUnitTypes::LayoutInches ) ); + //check size matches fixed item size converted to mm + QGSCOMPARENEAR( item->rect().width(), 2.0 * 25.4, 4 * DBL_EPSILON ); + QGSCOMPARENEAR( item->rect().height(), 4.0 * 25.4, 4 * DBL_EPSILON ); + //check that setting a fixed size applies this size immediately item->updateFixedSize( QgsLayoutSize( 150, 250, QgsUnitTypes::LayoutMillimeters ) ); QGSCOMPARENEAR( item->rect().width(), 150.0, 4 * DBL_EPSILON ); @@ -1119,8 +1124,8 @@ void TestQgsLayoutItem::minSize() //try to resize to less than minimum size fixedMinItem->attemptResize( QgsLayoutSize( 1.0, 0.5, QgsUnitTypes::LayoutPoints ) ); //check size matches fixed item size, not minimum size (converted to mm) - QGSCOMPARENEAR( fixedMinItem->rect().width(), 50.0, 4 * DBL_EPSILON ); - QGSCOMPARENEAR( fixedMinItem->rect().height(), 90.0, 4 * DBL_EPSILON ); + QGSCOMPARENEAR( fixedMinItem->rect().width(), 20.0, 4 * DBL_EPSILON ); + QGSCOMPARENEAR( fixedMinItem->rect().height(), 40.0, 4 * DBL_EPSILON ); } void TestQgsLayoutItem::move() diff --git a/tests/src/core/testqgslayouttable.cpp b/tests/src/core/testqgslayouttable.cpp new file mode 100644 index 00000000000..3fb8ef8e1cd --- /dev/null +++ b/tests/src/core/testqgslayouttable.cpp @@ -0,0 +1,1332 @@ +/*************************************************************************** + testqgslayouttable.cpp + ---------------------- + begin : November 2017 + copyright : (C) 2017 by Nyall Dawson + email : nyall dot dawson at gmail dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsapplication.h" +#include "qgslayout.h" +#include "qgslayoutitemmap.h" +#include "qgslayoutitemattributetable.h" +#include "qgslayouttablecolumn.h" +#include "qgslayoutframe.h" +#include "qgsmapsettings.h" +#include "qgsvectorlayer.h" +#include "qgsvectordataprovider.h" +#include "qgsfeature.h" +#include "qgsmultirenderchecker.h" +#include "qgsfontutils.h" +#include "qgsproject.h" +#include "qgsrelationmanager.h" +#include "qgsreadwritecontext.h" + +#include +#include "qgstest.h" + +class TestQgsLayoutTable : public QObject +{ + Q_OBJECT + + public: + TestQgsLayoutTable() = default; + + private slots: + void initTestCase();// will be called before the first testfunction is executed. + void cleanupTestCase();// will be called after the last testfunction was executed. + void init();// will be called before each testfunction is executed. + void cleanup();// will be called after every testfunction. + + void attributeTableHeadings(); //test retrieving attribute table headers + void attributeTableRows(); //test retrieving attribute table rows + void attributeTableFilterFeatures(); //test filtering attribute table rows + void attributeTableSetAttributes(); //test subset of attributes in table + void attributeTableVisibleOnly(); //test displaying only visible attributes + void attributeTableRender(); //test rendering attribute table + void manualColumnWidth(); //test setting manual column widths + void attributeTableEmpty(); //test empty modes for attribute table + void showEmptyRows(); //test showing empty rows + void attributeTableExtend(); + void attributeTableRepeat(); + void attributeTableAtlasSource(); //test attribute table in atlas feature mode + void attributeTableRelationSource(); //test attribute table in relation mode + void contentsContainsRow(); //test the contentsContainsRow function + void removeDuplicates(); //test removing duplicate rows + void multiLineText(); //test rendering a table with multiline text + void horizontalGrid(); //test rendering a table with horizontal-only grid + void verticalGrid(); //test rendering a table with vertical-only grid + void align(); //test alignment of table cells + void wrapChar(); //test setting wrap character + void autoWrap(); //test auto word wrap + void cellStyles(); //test cell styles + void cellStylesRender(); //test rendering cell styles + + private: + QgsVectorLayer *mVectorLayer = nullptr; + QString mReport; + + //compares rows in table to expected rows + void compareTable( QgsLayoutItemAttributeTable *table, const QVector &expectedRows ); +}; + +void TestQgsLayoutTable::initTestCase() +{ + QgsApplication::init(); + QgsApplication::initQgis(); + + //create maplayers from testdata and add to layer registry + QFileInfo vectorFileInfo( QStringLiteral( TEST_DATA_DIR ) + "/points.shp" ); + mVectorLayer = new QgsVectorLayer( vectorFileInfo.filePath(), + vectorFileInfo.completeBaseName(), + QStringLiteral( "ogr" ) ); + QgsProject::instance()->addMapLayer( mVectorLayer ); + + mReport = QStringLiteral( "

Layout Table Tests

\n" ); +} + +void TestQgsLayoutTable::cleanupTestCase() +{ + QString myReportFile = QDir::tempPath() + "/qgistest.html"; + QFile myFile( myReportFile ); + if ( myFile.open( QIODevice::WriteOnly | QIODevice::Append ) ) + { + QTextStream myQTextStream( &myFile ); + myQTextStream << mReport; + myFile.close(); + } + QgsApplication::exitQgis(); +} + +void TestQgsLayoutTable::init() +{ + QgsLayout l( QgsProject::instance() ); + l.initializeDefaults(); + QgsLayoutItemAttributeTable *table = new QgsLayoutItemAttributeTable( &l ); + QgsLayoutFrame *frame1 = new QgsLayoutFrame( &l, table ); + frame1->attemptSetSceneRect( QRectF( 5, 5, 100, 30 ) ); + QgsLayoutFrame *frame2 = new QgsLayoutFrame( &l, table ); + frame2->attemptSetSceneRect( QRectF( 5, 40, 100, 30 ) ); + frame1->setFrameEnabled( true ); + frame2->setFrameEnabled( true ); + table->addFrame( frame1 ); + table->addFrame( frame2 ); + table->setVectorLayer( mVectorLayer ); + table->setDisplayOnlyVisibleFeatures( false ); + table->setMaximumNumberOfFeatures( 10 ); + table->setContentFont( QgsFontUtils::getStandardTestFont() ); + table->setHeaderFont( QgsFontUtils::getStandardTestFont() ); + table->setBackgroundColor( Qt::yellow ); +} + +void TestQgsLayoutTable::cleanup() +{ +} + +void TestQgsLayoutTable::attributeTableHeadings() +{ + QgsLayout l( QgsProject::instance() ); + l.initializeDefaults(); + QgsLayoutItemAttributeTable *table = new QgsLayoutItemAttributeTable( &l ); + table->setVectorLayer( mVectorLayer ); + + //test retrieving attribute table headers + QStringList expectedHeaders; + expectedHeaders << QStringLiteral( "Class" ) << QStringLiteral( "Heading" ) << QStringLiteral( "Importance" ) << QStringLiteral( "Pilots" ) << QStringLiteral( "Cabin Crew" ) << QStringLiteral( "Staff" ); + + //get header labels and compare + QMap headerMap = table->headerLabels(); + QMap::const_iterator headerIt = headerMap.constBegin(); + QString expected; + QString evaluated; + for ( ; headerIt != headerMap.constEnd(); ++headerIt ) + { + evaluated = headerIt.value(); + expected = expectedHeaders.at( headerIt.key() ); + QCOMPARE( evaluated, expected ); + } +} + +void TestQgsLayoutTable::compareTable( QgsLayoutItemAttributeTable *table, const QVector &expectedRows ) +{ + //retrieve rows and check + QgsLayoutTableContents tableContents; + bool result = table->getTableContents( tableContents ); + QCOMPARE( result, true ); + + QgsLayoutTableContents::const_iterator resultIt = tableContents.constBegin(); + int rowNumber = 0; + int colNumber = 0; + + //check that number of rows matches expected + QCOMPARE( tableContents.count(), expectedRows.count() ); + + for ( ; resultIt != tableContents.constEnd(); ++resultIt ) + { + colNumber = 0; + QgsLayoutTableRow::const_iterator cellIt = ( *resultIt ).constBegin(); + for ( ; cellIt != ( *resultIt ).constEnd(); ++cellIt ) + { + QCOMPARE( ( *cellIt ).toString(), expectedRows.at( rowNumber ).at( colNumber ) ); + colNumber++; + } + //also check that number of columns matches expected + QCOMPARE( ( *resultIt ).count(), expectedRows.at( rowNumber ).count() ); + + rowNumber++; + } +} + +void TestQgsLayoutTable::attributeTableRows() +{ + //test retrieving attribute table rows + + QVector expectedRows; + QStringList row; + row << QStringLiteral( "Jet" ) << QStringLiteral( "90" ) << QStringLiteral( "3" ) << QStringLiteral( "2" ) << QStringLiteral( "0" ) << QStringLiteral( "2" ); + expectedRows.append( row ); + row.clear(); + row << QStringLiteral( "Biplane" ) << QStringLiteral( "0" ) << QStringLiteral( "1" ) << QStringLiteral( "3" ) << QStringLiteral( "3" ) << QStringLiteral( "6" ); + expectedRows.append( row ); + row.clear(); + row << QStringLiteral( "Jet" ) << QStringLiteral( "85" ) << QStringLiteral( "3" ) << QStringLiteral( "1" ) << QStringLiteral( "1" ) << QStringLiteral( "2" ); + expectedRows.append( row ); + + QgsLayout l( QgsProject::instance() ); + l.initializeDefaults(); + QgsLayoutItemAttributeTable *table = new QgsLayoutItemAttributeTable( &l ); + table->setVectorLayer( mVectorLayer ); + + //retrieve rows and check + table->setMaximumNumberOfFeatures( 3 ); + compareTable( table, expectedRows ); +} + +void TestQgsLayoutTable::attributeTableFilterFeatures() +{ + //test filtering attribute table rows + QgsLayout l( QgsProject::instance() ); + l.initializeDefaults(); + QgsLayoutItemAttributeTable *table = new QgsLayoutItemAttributeTable( &l ); + table->setVectorLayer( mVectorLayer ); + + table->setMaximumNumberOfFeatures( 10 ); + table->setFeatureFilter( QStringLiteral( "\"Class\"='B52'" ) ); + table->setFilterFeatures( true ); + + QVector expectedRows; + QStringList row; + row << QStringLiteral( "B52" ) << QStringLiteral( "0" ) << QStringLiteral( "10" ) << QStringLiteral( "2" ) << QStringLiteral( "1" ) << QStringLiteral( "3" ); + expectedRows.append( row ); + row.clear(); + row << QStringLiteral( "B52" ) << QStringLiteral( "12" ) << QStringLiteral( "10" ) << QStringLiteral( "1" ) << QStringLiteral( "1" ) << QStringLiteral( "2" ); + expectedRows.append( row ); + row.clear(); + row << QStringLiteral( "B52" ) << QStringLiteral( "34" ) << QStringLiteral( "10" ) << QStringLiteral( "2" ) << QStringLiteral( "1" ) << QStringLiteral( "3" ); + expectedRows.append( row ); + row.clear(); + row << QStringLiteral( "B52" ) << QStringLiteral( "80" ) << QStringLiteral( "10" ) << QStringLiteral( "2" ) << QStringLiteral( "1" ) << QStringLiteral( "3" ); + expectedRows.append( row ); + + //retrieve rows and check + compareTable( table, expectedRows ); +} + +void TestQgsLayoutTable::attributeTableSetAttributes() +{ + QgsLayout l( QgsProject::instance() ); + l.initializeDefaults(); + QgsLayoutItemAttributeTable *table = new QgsLayoutItemAttributeTable( &l ); + table->setVectorLayer( mVectorLayer ); + + //test subset of attributes in table + QStringList attributes; + attributes << QStringLiteral( "Class" ) << QStringLiteral( "Pilots" ) << QStringLiteral( "Cabin Crew" ); + table->setDisplayedFields( attributes ); + table->setMaximumNumberOfFeatures( 3 ); + + //check headers + QStringList expectedHeaders; + expectedHeaders << QStringLiteral( "Class" ) << QStringLiteral( "Pilots" ) << QStringLiteral( "Cabin Crew" ); + + //get header labels and compare + QMap headerMap = table->headerLabels(); + QMap::const_iterator headerIt = headerMap.constBegin(); + QString expected; + QString evaluated; + for ( ; headerIt != headerMap.constEnd(); ++headerIt ) + { + evaluated = headerIt.value(); + expected = expectedHeaders.at( headerIt.key() ); + QCOMPARE( evaluated, expected ); + } + + QVector expectedRows; + QStringList row; + row << QStringLiteral( "Jet" ) << QStringLiteral( "2" ) << QStringLiteral( "0" ); + expectedRows.append( row ); + row.clear(); + row << QStringLiteral( "Biplane" ) << QStringLiteral( "3" ) << QStringLiteral( "3" ); + expectedRows.append( row ); + row.clear(); + row << QStringLiteral( "Jet" ) << QStringLiteral( "1" ) << QStringLiteral( "1" ); + expectedRows.append( row ); + + //retrieve rows and check + compareTable( table, expectedRows ); +} + +void TestQgsLayoutTable::attributeTableVisibleOnly() +{ + //test displaying only visible attributes + QgsLayout l( QgsProject::instance() ); + l.initializeDefaults(); + QgsLayoutItemAttributeTable *table = new QgsLayoutItemAttributeTable( &l ); + table->setVectorLayer( mVectorLayer ); + + QgsLayoutItemMap *map = new QgsLayoutItemMap( &l ); + map->attemptSetSceneRect( QRectF( 20, 20, 200, 100 ) ); + map->setFrameEnabled( true ); + map->setExtent( QgsRectangle( -131.767, 30.558, -110.743, 41.070 ) ); + l.addLayoutItem( map ); + + table->setMap( map ); + table->setDisplayOnlyVisibleFeatures( true ); + + QVector expectedRows; + QStringList row; + row << QStringLiteral( "Jet" ) << QStringLiteral( "90" ) << QStringLiteral( "3" ) << QStringLiteral( "2" ) << QStringLiteral( "0" ) << QStringLiteral( "2" ); + expectedRows.append( row ); + row.clear(); + row << QStringLiteral( "Biplane" ) << QStringLiteral( "240" ) << QStringLiteral( "1" ) << QStringLiteral( "3" ) << QStringLiteral( "2" ) << QStringLiteral( "5" ); + expectedRows.append( row ); + row.clear(); + row << QStringLiteral( "Jet" ) << QStringLiteral( "180" ) << QStringLiteral( "3" ) << QStringLiteral( "1" ) << QStringLiteral( "0" ) << QStringLiteral( "1" ); + expectedRows.append( row ); + + //retrieve rows and check + compareTable( table, expectedRows ); +} + +void TestQgsLayoutTable::attributeTableRender() +{ + QgsLayout l( QgsProject::instance() ); + l.initializeDefaults(); + QgsLayoutItemAttributeTable *table = new QgsLayoutItemAttributeTable( &l ); + QgsLayoutFrame *frame1 = new QgsLayoutFrame( &l, table ); + frame1->attemptSetSceneRect( QRectF( 5, 5, 100, 30 ) ); + QgsLayoutFrame *frame2 = new QgsLayoutFrame( &l, table ); + frame2->attemptSetSceneRect( QRectF( 5, 40, 100, 30 ) ); + frame1->setFrameEnabled( true ); + frame2->setFrameEnabled( true ); + table->addFrame( frame1 ); + table->addFrame( frame2 ); + table->setVectorLayer( mVectorLayer ); + table->setDisplayOnlyVisibleFeatures( false ); + table->setMaximumNumberOfFeatures( 10 ); + table->setContentFont( QgsFontUtils::getStandardTestFont() ); + table->setHeaderFont( QgsFontUtils::getStandardTestFont() ); + table->setBackgroundColor( Qt::yellow ); + + table->setMaximumNumberOfFeatures( 20 ); + QgsLayoutChecker checker( QStringLiteral( "composerattributetable_render" ), &l ); + checker.setControlPathPrefix( QStringLiteral( "composer_table" ) ); + bool result = checker.testLayout( mReport ); + QVERIFY( result ); +} + +void TestQgsLayoutTable::manualColumnWidth() +{ + QgsLayout l( QgsProject::instance() ); + l.initializeDefaults(); + QgsLayoutItemAttributeTable *table = new QgsLayoutItemAttributeTable( &l ); + QgsLayoutFrame *frame1 = new QgsLayoutFrame( &l, table ); + frame1->attemptSetSceneRect( QRectF( 5, 5, 100, 30 ) ); + QgsLayoutFrame *frame2 = new QgsLayoutFrame( &l, table ); + frame2->attemptSetSceneRect( QRectF( 5, 40, 100, 30 ) ); + frame1->setFrameEnabled( true ); + frame2->setFrameEnabled( true ); + table->addFrame( frame1 ); + table->addFrame( frame2 ); + table->setVectorLayer( mVectorLayer ); + table->setDisplayOnlyVisibleFeatures( false ); + table->setMaximumNumberOfFeatures( 10 ); + table->setContentFont( QgsFontUtils::getStandardTestFont() ); + table->setHeaderFont( QgsFontUtils::getStandardTestFont() ); + table->setBackgroundColor( Qt::yellow ); + + table->setMaximumNumberOfFeatures( 20 ); + table->columns().at( 0 )->setWidth( 5 ); + QgsLayoutChecker checker( QStringLiteral( "composerattributetable_columnwidth" ), &l ); + checker.setControlPathPrefix( QStringLiteral( "composer_table" ) ); + bool result = checker.testLayout( mReport, 0 ); + QVERIFY( result ); +} + +void TestQgsLayoutTable::attributeTableEmpty() +{ + QgsLayout l( QgsProject::instance() ); + l.initializeDefaults(); + QgsLayoutItemAttributeTable *table = new QgsLayoutItemAttributeTable( &l ); + QgsLayoutFrame *frame1 = new QgsLayoutFrame( &l, table ); + frame1->attemptSetSceneRect( QRectF( 5, 5, 100, 30 ) ); + QgsLayoutFrame *frame2 = new QgsLayoutFrame( &l, table ); + frame2->attemptSetSceneRect( QRectF( 5, 40, 100, 30 ) ); + frame1->setFrameEnabled( true ); + frame2->setFrameEnabled( true ); + table->addFrame( frame1 ); + table->addFrame( frame2 ); + table->setVectorLayer( mVectorLayer ); + table->setDisplayOnlyVisibleFeatures( false ); + table->setMaximumNumberOfFeatures( 10 ); + table->setContentFont( QgsFontUtils::getStandardTestFont() ); + table->setHeaderFont( QgsFontUtils::getStandardTestFont() ); + table->setBackgroundColor( Qt::yellow ); + + table->setMaximumNumberOfFeatures( 20 ); + //hide all features from table + table->setFeatureFilter( QStringLiteral( "1=2" ) ); + table->setFilterFeatures( true ); + + table->setEmptyTableBehavior( QgsLayoutTable::HeadersOnly ); + QgsLayoutChecker checker( QStringLiteral( "composerattributetable_headersonly" ), &l ); + checker.setControlPathPrefix( QStringLiteral( "composer_table" ) ); + QVERIFY( checker.testLayout( mReport, 0 ) ); + + table->setEmptyTableBehavior( QgsLayoutTable::HideTable ); + QgsLayoutChecker checker2( QStringLiteral( "composerattributetable_hidetable" ), &l ); + checker2.setControlPathPrefix( QStringLiteral( "composer_table" ) ); + QVERIFY( checker2.testLayout( mReport, 0 ) ); + + table->setEmptyTableBehavior( QgsLayoutTable::ShowMessage ); + table->setEmptyTableMessage( QStringLiteral( "no rows" ) ); + QgsLayoutChecker checker3( QStringLiteral( "composerattributetable_showmessage" ), &l ); + checker3.setControlPathPrefix( QStringLiteral( "composer_table" ) ); + QVERIFY( checker3.testLayout( mReport, 0 ) ); +} + +void TestQgsLayoutTable::showEmptyRows() +{ + QgsLayout l( QgsProject::instance() ); + l.initializeDefaults(); + QgsLayoutItemAttributeTable *table = new QgsLayoutItemAttributeTable( &l ); + QgsLayoutFrame *frame1 = new QgsLayoutFrame( &l, table ); + frame1->attemptSetSceneRect( QRectF( 5, 5, 100, 30 ) ); + QgsLayoutFrame *frame2 = new QgsLayoutFrame( &l, table ); + frame2->attemptSetSceneRect( QRectF( 5, 40, 100, 30 ) ); + frame1->setFrameEnabled( true ); + frame2->setFrameEnabled( true ); + table->addFrame( frame1 ); + table->addFrame( frame2 ); + table->setVectorLayer( mVectorLayer ); + table->setDisplayOnlyVisibleFeatures( false ); + table->setMaximumNumberOfFeatures( 10 ); + table->setContentFont( QgsFontUtils::getStandardTestFont() ); + table->setHeaderFont( QgsFontUtils::getStandardTestFont() ); + table->setBackgroundColor( Qt::yellow ); + + table->setMaximumNumberOfFeatures( 3 ); + table->setShowEmptyRows( true ); + QgsLayoutChecker checker( QStringLiteral( "composerattributetable_drawempty" ), &l ); + checker.setControlPathPrefix( QStringLiteral( "composer_table" ) ); + QVERIFY( checker.testLayout( mReport, 0 ) ); +} + +void TestQgsLayoutTable::attributeTableExtend() +{ + QgsLayout l( QgsProject::instance() ); + l.initializeDefaults(); + QgsLayoutItemAttributeTable *table = new QgsLayoutItemAttributeTable( &l ); + QgsLayoutFrame *frame1 = new QgsLayoutFrame( &l, table ); + frame1->attemptSetSceneRect( QRectF( 5, 5, 100, 30 ) ); + QgsLayoutFrame *frame2 = new QgsLayoutFrame( &l, table ); + frame2->attemptSetSceneRect( QRectF( 5, 40, 100, 30 ) ); + frame1->setFrameEnabled( true ); + frame2->setFrameEnabled( true ); + table->addFrame( frame1 ); + table->addFrame( frame2 ); + table->setVectorLayer( mVectorLayer ); + table->setDisplayOnlyVisibleFeatures( false ); + table->setMaximumNumberOfFeatures( 10 ); + table->setContentFont( QgsFontUtils::getStandardTestFont() ); + table->setHeaderFont( QgsFontUtils::getStandardTestFont() ); + table->setBackgroundColor( Qt::yellow ); + + //test that adding and removing frames automatically does not result in a crash + table->removeFrame( 1 ); + + //force auto creation of some new frames + table->setResizeMode( QgsLayoutMultiFrame::ExtendToNextPage ); + + l.setSelectedItem( table->frame( 1 ) ); + + //now auto remove extra created frames + table->setMaximumNumberOfFeatures( 1 ); +} + +void TestQgsLayoutTable::attributeTableRepeat() +{ + QgsLayout l( QgsProject::instance() ); + l.initializeDefaults(); + QgsLayoutItemAttributeTable *table = new QgsLayoutItemAttributeTable( &l ); + QgsLayoutFrame *frame1 = new QgsLayoutFrame( &l, table ); + frame1->attemptSetSceneRect( QRectF( 5, 5, 100, 30 ) ); + QgsLayoutFrame *frame2 = new QgsLayoutFrame( &l, table ); + frame2->attemptSetSceneRect( QRectF( 5, 40, 100, 30 ) ); + frame1->setFrameEnabled( true ); + frame2->setFrameEnabled( true ); + table->addFrame( frame1 ); + table->addFrame( frame2 ); + table->setVectorLayer( mVectorLayer ); + table->setDisplayOnlyVisibleFeatures( false ); + table->setMaximumNumberOfFeatures( 10 ); + table->setContentFont( QgsFontUtils::getStandardTestFont() ); + table->setHeaderFont( QgsFontUtils::getStandardTestFont() ); + table->setBackgroundColor( Qt::yellow ); + + //test that creating and removing new frames in repeat mode does not crash + + table->setResizeMode( QgsLayoutMultiFrame::UseExistingFrames ); + //remove extra frames + for ( int idx = table->frameCount(); idx > 0; --idx ) + { + table->removeFrame( idx - 1 ); + } + + table->setMaximumNumberOfFeatures( 1 ); + + //force auto creation of some new frames + table->setResizeMode( QgsLayoutMultiFrame::RepeatUntilFinished ); + + for ( int features = 0; features < 50; ++features ) + { + table->setMaximumNumberOfFeatures( features ); + } + + //and then the reverse.... + for ( int features = 50; features > 1; --features ) + { + table->setMaximumNumberOfFeatures( features ); + } +} + +void TestQgsLayoutTable::attributeTableAtlasSource() +{ +#if 0 //TODO + QgsLayoutItemAttributeTable *table = new QgsLayoutItemAttributeTable( mComposition, false ); + + + table->setSource( QgsLayoutItemAttributeTable::AtlasFeature ); + + //setup atlas + QgsVectorLayer *vectorLayer = nullptr; + QFileInfo vectorFileInfo( QStringLiteral( TEST_DATA_DIR ) + "/points.shp" ); + vectorLayer = new QgsVectorLayer( vectorFileInfo.filePath(), + vectorFileInfo.completeBaseName(), + QStringLiteral( "ogr" ) ); + QgsProject::instance()->addMapLayer( vectorLayer ); + mComposition->atlasComposition().setCoverageLayer( vectorLayer ); + mComposition->atlasComposition().setEnabled( true ); + QVERIFY( mComposition->atlasComposition().beginRender() ); + + QVERIFY( mComposition->atlasComposition().prepareForFeature( 0 ) ); + QCOMPARE( table->contents()->length(), 1 ); + QgsComposerTableRow row = table->contents()->at( 0 ); + + //check a couple of results + QCOMPARE( row.at( 0 ), QVariant( "Jet" ) ); + QCOMPARE( row.at( 1 ), QVariant( 90 ) ); + QCOMPARE( row.at( 2 ), QVariant( 3 ) ); + QCOMPARE( row.at( 3 ), QVariant( 2 ) ); + QCOMPARE( row.at( 4 ), QVariant( 0 ) ); + QCOMPARE( row.at( 5 ), QVariant( 2 ) ); + + //next atlas feature + QVERIFY( mComposition->atlasComposition().prepareForFeature( 1 ) ); + QCOMPARE( table->contents()->length(), 1 ); + row = table->contents()->at( 0 ); + QCOMPARE( row.at( 0 ), QVariant( "Biplane" ) ); + QCOMPARE( row.at( 1 ), QVariant( 0 ) ); + QCOMPARE( row.at( 2 ), QVariant( 1 ) ); + QCOMPARE( row.at( 3 ), QVariant( 3 ) ); + QCOMPARE( row.at( 4 ), QVariant( 3 ) ); + QCOMPARE( row.at( 5 ), QVariant( 6 ) ); + + //next atlas feature + QVERIFY( mComposition->atlasComposition().prepareForFeature( 2 ) ); + QCOMPARE( table->contents()->length(), 1 ); + row = table->contents()->at( 0 ); + QCOMPARE( row.at( 0 ), QVariant( "Jet" ) ); + QCOMPARE( row.at( 1 ), QVariant( 85 ) ); + QCOMPARE( row.at( 2 ), QVariant( 3 ) ); + QCOMPARE( row.at( 3 ), QVariant( 1 ) ); + QCOMPARE( row.at( 4 ), QVariant( 1 ) ); + QCOMPARE( row.at( 5 ), QVariant( 2 ) ); + + mComposition->atlasComposition().endRender(); + + //try for a crash when removing current atlas layer + QgsProject::instance()->removeMapLayer( vectorLayer->id() ); + table->refreshAttributes(); + + mComposition->removeMultiFrame( table ); + delete table; +#endif +} + + +void TestQgsLayoutTable::attributeTableRelationSource() +{ + QgsLayout l( QgsProject::instance() ); + l.initializeDefaults(); + QgsLayoutItemAttributeTable *table = new QgsLayoutItemAttributeTable( &l ); + QgsLayoutFrame *frame1 = new QgsLayoutFrame( &l, table ); + frame1->attemptSetSceneRect( QRectF( 5, 5, 100, 30 ) ); + QgsLayoutFrame *frame2 = new QgsLayoutFrame( &l, table ); + frame2->attemptSetSceneRect( QRectF( 5, 40, 100, 30 ) ); + frame1->setFrameEnabled( true ); + frame2->setFrameEnabled( true ); + table->addFrame( frame1 ); + table->addFrame( frame2 ); + table->setVectorLayer( mVectorLayer ); + table->setDisplayOnlyVisibleFeatures( false ); + table->setMaximumNumberOfFeatures( 10 ); + table->setContentFont( QgsFontUtils::getStandardTestFont() ); + table->setHeaderFont( QgsFontUtils::getStandardTestFont() ); + table->setBackgroundColor( Qt::yellow ); + + QFileInfo vectorFileInfo( QStringLiteral( TEST_DATA_DIR ) + "/points_relations.shp" ); + QgsVectorLayer *atlasLayer = new QgsVectorLayer( vectorFileInfo.filePath(), + vectorFileInfo.completeBaseName(), + QStringLiteral( "ogr" ) ); + + QgsProject::instance()->addMapLayer( atlasLayer ); + +#if 0 //TODO + //setup atlas + mComposition->atlasComposition().setCoverageLayer( atlasLayer ); + mComposition->atlasComposition().setEnabled( true ); + + //create a relation + QgsRelation relation; + relation.setId( QStringLiteral( "testrelation" ) ); + relation.setReferencedLayer( atlasLayer->id() ); + relation.setReferencingLayer( mVectorLayer->id() ); + relation.addFieldPair( QStringLiteral( "Class" ), QStringLiteral( "Class" ) ); + QgsProject::instance()->relationManager()->addRelation( relation ); + + QgsLayoutItemAttributeTable *table = new QgsLayoutItemAttributeTable( mComposition, false ); + table->setMaximumNumberOfFeatures( 50 ); + table->setSource( QgsLayoutItemAttributeTable::RelationChildren ); + table->setRelationId( relation.id() ); + + QVERIFY( mComposition->atlasComposition().beginRender() ); + QVERIFY( mComposition->atlasComposition().prepareForFeature( 0 ) ); + + QCOMPARE( mComposition->atlasComposition().feature().attribute( "Class" ).toString(), QString( "Jet" ) ); + QCOMPARE( table->contents()->length(), 8 ); + + QgsComposerTableRow row = table->contents()->at( 0 ); + + //check a couple of results + QCOMPARE( row.at( 0 ), QVariant( "Jet" ) ); + QCOMPARE( row.at( 1 ), QVariant( 90 ) ); + QCOMPARE( row.at( 2 ), QVariant( 3 ) ); + QCOMPARE( row.at( 3 ), QVariant( 2 ) ); + QCOMPARE( row.at( 4 ), QVariant( 0 ) ); + QCOMPARE( row.at( 5 ), QVariant( 2 ) ); + row = table->contents()->at( 1 ); + QCOMPARE( row.at( 0 ), QVariant( "Jet" ) ); + QCOMPARE( row.at( 1 ), QVariant( 85 ) ); + QCOMPARE( row.at( 2 ), QVariant( 3 ) ); + QCOMPARE( row.at( 3 ), QVariant( 1 ) ); + QCOMPARE( row.at( 4 ), QVariant( 1 ) ); + QCOMPARE( row.at( 5 ), QVariant( 2 ) ); + row = table->contents()->at( 2 ); + QCOMPARE( row.at( 0 ), QVariant( "Jet" ) ); + QCOMPARE( row.at( 1 ), QVariant( 95 ) ); + QCOMPARE( row.at( 2 ), QVariant( 3 ) ); + QCOMPARE( row.at( 3 ), QVariant( 1 ) ); + QCOMPARE( row.at( 4 ), QVariant( 1 ) ); + QCOMPARE( row.at( 5 ), QVariant( 2 ) ); + + //next atlas feature + QVERIFY( mComposition->atlasComposition().prepareForFeature( 1 ) ); + QCOMPARE( mComposition->atlasComposition().feature().attribute( "Class" ).toString(), QString( "Biplane" ) ); + QCOMPARE( table->contents()->length(), 5 ); + row = table->contents()->at( 0 ); + QCOMPARE( row.at( 0 ), QVariant( "Biplane" ) ); + QCOMPARE( row.at( 1 ), QVariant( 0 ) ); + QCOMPARE( row.at( 2 ), QVariant( 1 ) ); + QCOMPARE( row.at( 3 ), QVariant( 3 ) ); + QCOMPARE( row.at( 4 ), QVariant( 3 ) ); + QCOMPARE( row.at( 5 ), QVariant( 6 ) ); + row = table->contents()->at( 1 ); + QCOMPARE( row.at( 0 ), QVariant( "Biplane" ) ); + QCOMPARE( row.at( 1 ), QVariant( 340 ) ); + QCOMPARE( row.at( 2 ), QVariant( 1 ) ); + QCOMPARE( row.at( 3 ), QVariant( 3 ) ); + QCOMPARE( row.at( 4 ), QVariant( 3 ) ); + QCOMPARE( row.at( 5 ), QVariant( 6 ) ); + + mComposition->atlasComposition().endRender(); + + //try for a crash when removing current atlas layer + QgsProject::instance()->removeMapLayer( atlasLayer->id() ); + + table->refreshAttributes(); + + mComposition->removeMultiFrame( table ); + delete table; +#endif +} + +void TestQgsLayoutTable::contentsContainsRow() +{ + QgsLayout l( QgsProject::instance() ); + l.initializeDefaults(); + QgsLayoutItemAttributeTable *table = new QgsLayoutItemAttributeTable( &l ); + table->setVectorLayer( mVectorLayer ); + table->setDisplayOnlyVisibleFeatures( false ); + table->setMaximumNumberOfFeatures( 10 ); + + QgsLayoutTableContents testContents; + QgsLayoutTableRow row1; + row1 << QVariant( QStringLiteral( "string 1" ) ) << QVariant( 2 ) << QVariant( 1.5 ) << QVariant( QStringLiteral( "string 2" ) ); + QgsLayoutTableRow row2; + row2 << QVariant( QStringLiteral( "string 2" ) ) << QVariant( 2 ) << QVariant( 1.5 ) << QVariant( QStringLiteral( "string 2" ) ); + //same as row1 + QgsLayoutTableRow row3; + row3 << QVariant( QStringLiteral( "string 1" ) ) << QVariant( 2 ) << QVariant( 1.5 ) << QVariant( QStringLiteral( "string 2" ) ); + QgsLayoutTableRow row4; + row4 << QVariant( QStringLiteral( "string 1" ) ) << QVariant( 2 ) << QVariant( 1.7 ) << QVariant( QStringLiteral( "string 2" ) ); + + testContents << row1; + testContents << row2; + + QVERIFY( table->contentsContainsRow( testContents, row1 ) ); + QVERIFY( table->contentsContainsRow( testContents, row2 ) ); + QVERIFY( table->contentsContainsRow( testContents, row3 ) ); + QVERIFY( !table->contentsContainsRow( testContents, row4 ) ); +} + +void TestQgsLayoutTable::removeDuplicates() +{ + QgsVectorLayer *dupesLayer = new QgsVectorLayer( QStringLiteral( "Point?field=col1:integer&field=col2:integer&field=col3:integer" ), QStringLiteral( "dupes" ), QStringLiteral( "memory" ) ); + QVERIFY( dupesLayer->isValid() ); + QgsFeature f1( dupesLayer->dataProvider()->fields(), 1 ); + f1.setAttribute( QStringLiteral( "col1" ), 1 ); + f1.setAttribute( QStringLiteral( "col2" ), 1 ); + f1.setAttribute( QStringLiteral( "col3" ), 1 ); + QgsFeature f2( dupesLayer->dataProvider()->fields(), 2 ); + f2.setAttribute( QStringLiteral( "col1" ), 1 ); + f2.setAttribute( QStringLiteral( "col2" ), 2 ); + f2.setAttribute( QStringLiteral( "col3" ), 2 ); + QgsFeature f3( dupesLayer->dataProvider()->fields(), 3 ); + f3.setAttribute( QStringLiteral( "col1" ), 1 ); + f3.setAttribute( QStringLiteral( "col2" ), 2 ); + f3.setAttribute( QStringLiteral( "col3" ), 3 ); + QgsFeature f4( dupesLayer->dataProvider()->fields(), 4 ); + f4.setAttribute( QStringLiteral( "col1" ), 1 ); + f4.setAttribute( QStringLiteral( "col2" ), 1 ); + f4.setAttribute( QStringLiteral( "col3" ), 1 ); + dupesLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 << f2 << f3 << f4 ); + + QgsLayout l( QgsProject::instance() ); + l.initializeDefaults(); + QgsLayoutItemAttributeTable *table = new QgsLayoutItemAttributeTable( &l ); + table->setSource( QgsLayoutItemAttributeTable::LayerAttributes ); + table->setVectorLayer( dupesLayer ); + table->setMaximumNumberOfFeatures( 50 ); + QCOMPARE( table->contents().length(), 4 ); + + table->setUniqueRowsOnly( true ); + QCOMPARE( table->contents().length(), 3 ); + + //check if removing attributes in unique mode works correctly (should result in duplicate rows, + //which will be stripped out) + delete table->columns().takeLast(); + table->refreshAttributes(); + QCOMPARE( table->contents().length(), 2 ); + delete table->columns().takeLast(); + table->refreshAttributes(); + QCOMPARE( table->contents().length(), 1 ); + table->setUniqueRowsOnly( false ); + QCOMPARE( table->contents().length(), 4 ); + + delete dupesLayer; +} + +void TestQgsLayoutTable::multiLineText() +{ + QgsLayout l( QgsProject::instance() ); + l.initializeDefaults(); + QgsLayoutItemAttributeTable *table = new QgsLayoutItemAttributeTable( &l ); + QgsLayoutFrame *frame1 = new QgsLayoutFrame( &l, table ); + frame1->attemptSetSceneRect( QRectF( 5, 5, 100, 30 ) ); + QgsLayoutFrame *frame2 = new QgsLayoutFrame( &l, table ); + frame2->attemptSetSceneRect( QRectF( 5, 40, 100, 30 ) ); + frame1->setFrameEnabled( true ); + frame2->setFrameEnabled( true ); + table->addFrame( frame1 ); + table->addFrame( frame2 ); + table->setVectorLayer( mVectorLayer ); + table->setDisplayOnlyVisibleFeatures( false ); + table->setMaximumNumberOfFeatures( 10 ); + table->setContentFont( QgsFontUtils::getStandardTestFont() ); + table->setHeaderFont( QgsFontUtils::getStandardTestFont() ); + table->setBackgroundColor( Qt::yellow ); + + QgsVectorLayer *multiLineLayer = new QgsVectorLayer( QStringLiteral( "Point?field=col1:string&field=col2:string&field=col3:string" ), QStringLiteral( "multiline" ), QStringLiteral( "memory" ) ); + QVERIFY( multiLineLayer->isValid() ); + QgsFeature f1( multiLineLayer->dataProvider()->fields(), 1 ); + f1.setAttribute( QStringLiteral( "col1" ), "multiline\nstring" ); + f1.setAttribute( QStringLiteral( "col2" ), "singleline string" ); + f1.setAttribute( QStringLiteral( "col3" ), "singleline" ); + QgsFeature f2( multiLineLayer->dataProvider()->fields(), 2 ); + f2.setAttribute( QStringLiteral( "col1" ), "singleline string" ); + f2.setAttribute( QStringLiteral( "col2" ), "multiline\nstring" ); + f2.setAttribute( QStringLiteral( "col3" ), "singleline" ); + QgsFeature f3( multiLineLayer->dataProvider()->fields(), 3 ); + f3.setAttribute( QStringLiteral( "col1" ), "singleline" ); + f3.setAttribute( QStringLiteral( "col2" ), "singleline" ); + f3.setAttribute( QStringLiteral( "col3" ), "multiline\nstring" ); + QgsFeature f4( multiLineLayer->dataProvider()->fields(), 4 ); + f4.setAttribute( QStringLiteral( "col1" ), "long triple\nline\nstring" ); + f4.setAttribute( QStringLiteral( "col2" ), "double\nlinestring" ); + f4.setAttribute( QStringLiteral( "col3" ), "singleline" ); + multiLineLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 << f2 << f3 << f4 ); + + frame2->attemptSetSceneRect( QRectF( 5, 40, 100, 90 ) ); + + table->setMaximumNumberOfFeatures( 20 ); + table->setVectorLayer( multiLineLayer ); + QgsLayoutChecker checker( QStringLiteral( "composerattributetable_multiline" ), &l ); + checker.setControlPathPrefix( QStringLiteral( "composer_table" ) ); + bool result = checker.testLayout( mReport ); + QVERIFY( result ); + + delete multiLineLayer; +} + +void TestQgsLayoutTable::horizontalGrid() +{ + QgsLayout l( QgsProject::instance() ); + l.initializeDefaults(); + QgsLayoutItemAttributeTable *table = new QgsLayoutItemAttributeTable( &l ); + QgsLayoutFrame *frame1 = new QgsLayoutFrame( &l, table ); + frame1->attemptSetSceneRect( QRectF( 5, 5, 100, 30 ) ); + QgsLayoutFrame *frame2 = new QgsLayoutFrame( &l, table ); + frame2->attemptSetSceneRect( QRectF( 5, 40, 100, 30 ) ); + frame1->setFrameEnabled( true ); + frame2->setFrameEnabled( true ); + table->addFrame( frame1 ); + table->addFrame( frame2 ); + table->setVectorLayer( mVectorLayer ); + table->setDisplayOnlyVisibleFeatures( false ); + table->setMaximumNumberOfFeatures( 10 ); + table->setContentFont( QgsFontUtils::getStandardTestFont() ); + table->setHeaderFont( QgsFontUtils::getStandardTestFont() ); + table->setBackgroundColor( Qt::yellow ); + + QgsVectorLayer *multiLineLayer = new QgsVectorLayer( QStringLiteral( "Point?field=col1:string&field=col2:string&field=col3:string" ), QStringLiteral( "multiline" ), QStringLiteral( "memory" ) ); + QVERIFY( multiLineLayer->isValid() ); + QgsFeature f1( multiLineLayer->dataProvider()->fields(), 1 ); + f1.setAttribute( QStringLiteral( "col1" ), "multiline\nstring" ); + f1.setAttribute( QStringLiteral( "col2" ), "singleline string" ); + f1.setAttribute( QStringLiteral( "col3" ), "singleline" ); + QgsFeature f2( multiLineLayer->dataProvider()->fields(), 2 ); + f2.setAttribute( QStringLiteral( "col1" ), "singleline string" ); + f2.setAttribute( QStringLiteral( "col2" ), "multiline\nstring" ); + f2.setAttribute( QStringLiteral( "col3" ), "singleline" ); + QgsFeature f3( multiLineLayer->dataProvider()->fields(), 3 ); + f3.setAttribute( QStringLiteral( "col1" ), "singleline" ); + f3.setAttribute( QStringLiteral( "col2" ), "singleline" ); + f3.setAttribute( QStringLiteral( "col3" ), "multiline\nstring" ); + QgsFeature f4( multiLineLayer->dataProvider()->fields(), 4 ); + f4.setAttribute( QStringLiteral( "col1" ), "long triple\nline\nstring" ); + f4.setAttribute( QStringLiteral( "col2" ), "double\nlinestring" ); + f4.setAttribute( QStringLiteral( "col3" ), "singleline" ); + multiLineLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 << f2 << f3 << f4 ); + + frame1->setFrameEnabled( false ); + frame2->setFrameEnabled( false ); + frame2->attemptSetSceneRect( QRectF( 5, 40, 100, 90 ) ); + + table->setMaximumNumberOfFeatures( 20 ); + table->setShowGrid( true ); + table->setHorizontalGrid( true ); + table->setVerticalGrid( false ); + table->setVectorLayer( multiLineLayer ); + QgsLayoutChecker checker( QStringLiteral( "composerattributetable_horizontalgrid" ), &l ); + checker.setControlPathPrefix( QStringLiteral( "composer_table" ) ); + bool result = checker.testLayout( mReport ); + QVERIFY( result ); + + delete multiLineLayer; +} + +void TestQgsLayoutTable::verticalGrid() +{ + QgsLayout l( QgsProject::instance() ); + l.initializeDefaults(); + QgsLayoutItemAttributeTable *table = new QgsLayoutItemAttributeTable( &l ); + QgsLayoutFrame *frame1 = new QgsLayoutFrame( &l, table ); + frame1->attemptSetSceneRect( QRectF( 5, 5, 100, 30 ) ); + QgsLayoutFrame *frame2 = new QgsLayoutFrame( &l, table ); + frame2->attemptSetSceneRect( QRectF( 5, 40, 100, 30 ) ); + frame1->setFrameEnabled( true ); + frame2->setFrameEnabled( true ); + table->addFrame( frame1 ); + table->addFrame( frame2 ); + table->setVectorLayer( mVectorLayer ); + table->setDisplayOnlyVisibleFeatures( false ); + table->setMaximumNumberOfFeatures( 10 ); + table->setContentFont( QgsFontUtils::getStandardTestFont() ); + table->setHeaderFont( QgsFontUtils::getStandardTestFont() ); + table->setBackgroundColor( Qt::yellow ); + + QgsVectorLayer *multiLineLayer = new QgsVectorLayer( QStringLiteral( "Point?field=col1:string&field=col2:string&field=col3:string" ), QStringLiteral( "multiline" ), QStringLiteral( "memory" ) ); + QVERIFY( multiLineLayer->isValid() ); + QgsFeature f1( multiLineLayer->dataProvider()->fields(), 1 ); + f1.setAttribute( QStringLiteral( "col1" ), "multiline\nstring" ); + f1.setAttribute( QStringLiteral( "col2" ), "singleline string" ); + f1.setAttribute( QStringLiteral( "col3" ), "singleline" ); + QgsFeature f2( multiLineLayer->dataProvider()->fields(), 2 ); + f2.setAttribute( QStringLiteral( "col1" ), "singleline string" ); + f2.setAttribute( QStringLiteral( "col2" ), "multiline\nstring" ); + f2.setAttribute( QStringLiteral( "col3" ), "singleline" ); + QgsFeature f3( multiLineLayer->dataProvider()->fields(), 3 ); + f3.setAttribute( QStringLiteral( "col1" ), "singleline" ); + f3.setAttribute( QStringLiteral( "col2" ), "singleline" ); + f3.setAttribute( QStringLiteral( "col3" ), "multiline\nstring" ); + QgsFeature f4( multiLineLayer->dataProvider()->fields(), 4 ); + f4.setAttribute( QStringLiteral( "col1" ), "long triple\nline\nstring" ); + f4.setAttribute( QStringLiteral( "col2" ), "double\nlinestring" ); + f4.setAttribute( QStringLiteral( "col3" ), "singleline" ); + multiLineLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 << f2 << f3 << f4 ); + + frame1->setFrameEnabled( false ); + frame2->setFrameEnabled( false ); + frame2->attemptSetSceneRect( QRectF( 5, 40, 100, 90 ) ); + + table->setMaximumNumberOfFeatures( 20 ); + table->setShowGrid( true ); + table->setHorizontalGrid( false ); + table->setVerticalGrid( true ); + table->setVectorLayer( multiLineLayer ); + QgsLayoutChecker checker( QStringLiteral( "composerattributetable_verticalgrid" ), &l ); + checker.setControlPathPrefix( QStringLiteral( "composer_table" ) ); + bool result = checker.testLayout( mReport ); + QVERIFY( result ); + + delete multiLineLayer; +} + +void TestQgsLayoutTable::align() +{ + QgsLayout l( QgsProject::instance() ); + l.initializeDefaults(); + QgsLayoutItemAttributeTable *table = new QgsLayoutItemAttributeTable( &l ); + QgsLayoutFrame *frame1 = new QgsLayoutFrame( &l, table ); + frame1->attemptSetSceneRect( QRectF( 5, 5, 100, 30 ) ); + QgsLayoutFrame *frame2 = new QgsLayoutFrame( &l, table ); + frame2->attemptSetSceneRect( QRectF( 5, 40, 100, 30 ) ); + frame1->setFrameEnabled( true ); + frame2->setFrameEnabled( true ); + table->addFrame( frame1 ); + table->addFrame( frame2 ); + table->setVectorLayer( mVectorLayer ); + table->setDisplayOnlyVisibleFeatures( false ); + table->setMaximumNumberOfFeatures( 10 ); + table->setContentFont( QgsFontUtils::getStandardTestFont() ); + table->setHeaderFont( QgsFontUtils::getStandardTestFont() ); + table->setBackgroundColor( Qt::yellow ); + + QgsVectorLayer *multiLineLayer = new QgsVectorLayer( QStringLiteral( "Point?field=col1:string&field=col2:string&field=col3:string" ), QStringLiteral( "multiline" ), QStringLiteral( "memory" ) ); + QVERIFY( multiLineLayer->isValid() ); + QgsFeature f1( multiLineLayer->dataProvider()->fields(), 1 ); + f1.setAttribute( QStringLiteral( "col1" ), "multiline\nstring" ); + f1.setAttribute( QStringLiteral( "col2" ), "singleline string" ); + f1.setAttribute( QStringLiteral( "col3" ), "singleline" ); + QgsFeature f2( multiLineLayer->dataProvider()->fields(), 2 ); + f2.setAttribute( QStringLiteral( "col1" ), "singleline string" ); + f2.setAttribute( QStringLiteral( "col2" ), "multiline\nstring" ); + f2.setAttribute( QStringLiteral( "col3" ), "singleline" ); + QgsFeature f3( multiLineLayer->dataProvider()->fields(), 3 ); + f3.setAttribute( QStringLiteral( "col1" ), "singleline" ); + f3.setAttribute( QStringLiteral( "col2" ), "singleline" ); + f3.setAttribute( QStringLiteral( "col3" ), "multiline\nstring" ); + QgsFeature f4( multiLineLayer->dataProvider()->fields(), 4 ); + f4.setAttribute( QStringLiteral( "col1" ), "long triple\nline\nstring" ); + f4.setAttribute( QStringLiteral( "col2" ), "double\nlinestring" ); + f4.setAttribute( QStringLiteral( "col3" ), "singleline" ); + multiLineLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 << f2 << f3 << f4 ); + + frame2->attemptSetSceneRect( QRectF( 5, 40, 100, 90 ) ); + + table->setMaximumNumberOfFeatures( 20 ); + table->setVectorLayer( multiLineLayer ); + + table->columns().at( 0 )->setHAlignment( Qt::AlignLeft ); + table->columns().at( 0 )->setVAlignment( Qt::AlignTop ); + table->columns().at( 1 )->setHAlignment( Qt::AlignHCenter ); + table->columns().at( 1 )->setVAlignment( Qt::AlignVCenter ); + table->columns().at( 2 )->setHAlignment( Qt::AlignRight ); + table->columns(). at( 2 )->setVAlignment( Qt::AlignBottom ); + QgsLayoutChecker checker( QStringLiteral( "composerattributetable_align" ), &l ); + checker.setControlPathPrefix( QStringLiteral( "composer_table" ) ); + bool result = checker.testLayout( mReport ); + QVERIFY( result ); + + delete multiLineLayer; +} + +void TestQgsLayoutTable::wrapChar() +{ + QgsLayout l( QgsProject::instance() ); + l.initializeDefaults(); + QgsLayoutItemAttributeTable *table = new QgsLayoutItemAttributeTable( &l ); + table->setVectorLayer( mVectorLayer ); + table->setDisplayOnlyVisibleFeatures( false ); + table->setMaximumNumberOfFeatures( 10 ); + table->setContentFont( QgsFontUtils::getStandardTestFont() ); + table->setHeaderFont( QgsFontUtils::getStandardTestFont() ); + table->setBackgroundColor( Qt::yellow ); + + QgsVectorLayer *multiLineLayer = new QgsVectorLayer( QStringLiteral( "Point?field=col1:string&field=col2:string&field=col3:string" ), QStringLiteral( "multiline" ), QStringLiteral( "memory" ) ); + QVERIFY( multiLineLayer->isValid() ); + QgsFeature f1( multiLineLayer->dataProvider()->fields(), 1 ); + f1.setAttribute( QStringLiteral( "col1" ), "multiline\nstring" ); + f1.setAttribute( QStringLiteral( "col2" ), "singleline string" ); + f1.setAttribute( QStringLiteral( "col3" ), "singleline" ); + multiLineLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 ); + + table->setMaximumNumberOfFeatures( 1 ); + table->setVectorLayer( multiLineLayer ); + table->setWrapString( QStringLiteral( "in" ) ); + + QVector expectedRows; + QStringList row; + row << QStringLiteral( "multil\ne\nstr\ng" ) << QStringLiteral( "s\nglel\ne str\ng" ) << QStringLiteral( "s\nglel\ne" ); + expectedRows.append( row ); + + //retrieve rows and check + compareTable( table, expectedRows ); +} + +void TestQgsLayoutTable::autoWrap() +{ + QgsLayout l( QgsProject::instance() ); + l.initializeDefaults(); + QgsLayoutItemAttributeTable *table = new QgsLayoutItemAttributeTable( &l ); + QgsLayoutFrame *frame1 = new QgsLayoutFrame( &l, table ); + frame1->attemptSetSceneRect( QRectF( 5, 5, 100, 30 ) ); + QgsLayoutFrame *frame2 = new QgsLayoutFrame( &l, table ); + frame2->attemptSetSceneRect( QRectF( 5, 40, 100, 30 ) ); + frame1->setFrameEnabled( true ); + frame2->setFrameEnabled( true ); + table->addFrame( frame1 ); + table->addFrame( frame2 ); + table->setVectorLayer( mVectorLayer ); + table->setDisplayOnlyVisibleFeatures( false ); + table->setMaximumNumberOfFeatures( 10 ); + table->setContentFont( QgsFontUtils::getStandardTestFont() ); + table->setHeaderFont( QgsFontUtils::getStandardTestFont() ); + table->setBackgroundColor( Qt::yellow ); + + QgsVectorLayer *multiLineLayer = new QgsVectorLayer( QStringLiteral( "Point?field=col1:string&field=col2:string&field=col3:string" ), QStringLiteral( "multiline" ), QStringLiteral( "memory" ) ); + QVERIFY( multiLineLayer->isValid() ); + QgsFeature f1( multiLineLayer->dataProvider()->fields(), 1 ); + f1.setAttribute( QStringLiteral( "col1" ), "long multiline\nstring" ); + f1.setAttribute( QStringLiteral( "col2" ), "singleline string" ); + f1.setAttribute( QStringLiteral( "col3" ), "singleline" ); + QgsFeature f2( multiLineLayer->dataProvider()->fields(), 2 ); + f2.setAttribute( QStringLiteral( "col1" ), "singleline string" ); + f2.setAttribute( QStringLiteral( "col2" ), "multiline\nstring" ); + f2.setAttribute( QStringLiteral( "col3" ), "singleline" ); + QgsFeature f3( multiLineLayer->dataProvider()->fields(), 3 ); + f3.setAttribute( QStringLiteral( "col1" ), "singleline" ); + f3.setAttribute( QStringLiteral( "col2" ), "singleline" ); + f3.setAttribute( QStringLiteral( "col3" ), "multiline\nstring" ); + QgsFeature f4( multiLineLayer->dataProvider()->fields(), 4 ); + f4.setAttribute( QStringLiteral( "col1" ), "a bit long triple line string" ); + f4.setAttribute( QStringLiteral( "col2" ), "double toolongtofitononeline string with some more lines on the end andanotherreallylongline" ); + f4.setAttribute( QStringLiteral( "col3" ), "singleline" ); + multiLineLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 << f2 << f3 << f4 ); + + frame2->attemptSetSceneRect( QRectF( 5, 40, 100, 90 ) ); + + table->setMaximumNumberOfFeatures( 20 ); + table->setVectorLayer( multiLineLayer ); + table->setWrapBehavior( QgsLayoutTable::WrapText ); + + table->columns().at( 0 )->setWidth( 25 ); + table->columns().at( 1 )->setWidth( 25 ); + QgsLayoutChecker checker( QStringLiteral( "composerattributetable_autowrap" ), &l ); + checker.setControlPathPrefix( QStringLiteral( "composer_table" ) ); + bool result = checker.testLayout( mReport, 0 ); + QVERIFY( result ); +} + +void TestQgsLayoutTable::cellStyles() +{ + QgsLayout l( QgsProject::instance() ); + l.initializeDefaults(); + QgsLayoutItemAttributeTable *table = new QgsLayoutItemAttributeTable( &l ); + QgsLayoutFrame *frame1 = new QgsLayoutFrame( &l, table ); + frame1->attemptSetSceneRect( QRectF( 5, 5, 100, 30 ) ); + QgsLayoutFrame *frame2 = new QgsLayoutFrame( &l, table ); + frame2->attemptSetSceneRect( QRectF( 5, 40, 100, 30 ) ); + frame1->setFrameEnabled( true ); + frame2->setFrameEnabled( true ); + table->addFrame( frame1 ); + table->addFrame( frame2 ); + table->setVectorLayer( mVectorLayer ); + table->setDisplayOnlyVisibleFeatures( false ); + table->setMaximumNumberOfFeatures( 10 ); + table->setContentFont( QgsFontUtils::getStandardTestFont() ); + table->setHeaderFont( QgsFontUtils::getStandardTestFont() ); + table->setBackgroundColor( Qt::yellow ); + + QgsLayoutTableStyle original; + original.enabled = true; + original.cellBackgroundColor = QColor( 200, 100, 150, 90 ); + + //write to xml + QDomImplementation DomImplementation; + QDomDocumentType documentType = + DomImplementation.createDocumentType( + QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ), QStringLiteral( "SYSTEM" ) ); + QDomDocument doc( documentType ); + + //test writing with no node + QDomElement node = doc.createElement( QStringLiteral( "style" ) ); + QVERIFY( original.writeXml( node, doc ) ); + + //read from xml + QgsLayoutTableStyle styleFromXml; + QVERIFY( styleFromXml.readXml( node ) ); + + //check + QCOMPARE( original.enabled, styleFromXml.enabled ); + QCOMPARE( original.cellBackgroundColor, styleFromXml.cellBackgroundColor ); + + + // check writing/reading whole set of styles + QgsLayoutItemAttributeTable *originalTable = new QgsLayoutItemAttributeTable( &l ); + + QgsLayoutTableStyle style1; + style1.enabled = true; + style1.cellBackgroundColor = QColor( 25, 50, 75, 100 ); + originalTable->setCellStyle( QgsLayoutTable::FirstRow, style1 ); + QgsLayoutTableStyle style2; + style1.enabled = false; + style1.cellBackgroundColor = QColor( 60, 62, 64, 68 ); + originalTable->setCellStyle( QgsLayoutTable::LastColumn, style2 ); + + //write to XML + QDomElement tableElement = doc.createElement( QStringLiteral( "table" ) ); + QVERIFY( originalTable->writeXml( tableElement, doc, QgsReadWriteContext(), true ) ); + + //read from XML + QgsLayoutItemAttributeTable *tableFromXml = new QgsLayoutItemAttributeTable( &l ); + QVERIFY( tableFromXml->readXml( tableElement.firstChildElement(), doc, QgsReadWriteContext(), true ) ); + + //check that styles were correctly read + QCOMPARE( tableFromXml->cellStyle( QgsLayoutTable::FirstRow )->enabled, originalTable->cellStyle( QgsLayoutTable::FirstRow )->enabled ); + QCOMPARE( tableFromXml->cellStyle( QgsLayoutTable::FirstRow )->cellBackgroundColor, originalTable->cellStyle( QgsLayoutTable::FirstRow )->cellBackgroundColor ); + QCOMPARE( tableFromXml->cellStyle( QgsLayoutTable::LastColumn )->enabled, originalTable->cellStyle( QgsLayoutTable::LastColumn )->enabled ); + QCOMPARE( tableFromXml->cellStyle( QgsLayoutTable::LastColumn )->cellBackgroundColor, originalTable->cellStyle( QgsLayoutTable::LastColumn )->cellBackgroundColor ); + + //check backgroundColor method + //build up rules in descending order of precedence + table->setBackgroundColor( QColor( 50, 50, 50, 50 ) ); + QgsLayoutTableStyle style; + style.enabled = true; + style.cellBackgroundColor = QColor( 25, 50, 75, 100 ); + table->setCellStyle( QgsLayoutTable::OddColumns, style ); + QCOMPARE( table->backgroundColor( 0, 0 ), QColor( 25, 50, 75, 100 ) ); + QCOMPARE( table->backgroundColor( 0, 1 ), QColor( 50, 50, 50, 50 ) ); + QCOMPARE( table->backgroundColor( 0, 2 ), QColor( 25, 50, 75, 100 ) ); + QCOMPARE( table->backgroundColor( 0, 3 ), QColor( 50, 50, 50, 50 ) ); + QCOMPARE( table->backgroundColor( 1, 0 ), QColor( 25, 50, 75, 100 ) ); + QCOMPARE( table->backgroundColor( 1, 1 ), QColor( 50, 50, 50, 50 ) ); + QCOMPARE( table->backgroundColor( 1, 2 ), QColor( 25, 50, 75, 100 ) ); + QCOMPARE( table->backgroundColor( 1, 3 ), QColor( 50, 50, 50, 50 ) ); + style.cellBackgroundColor = QColor( 30, 80, 90, 23 ); + table->setCellStyle( QgsLayoutTable::EvenColumns, style ); + QCOMPARE( table->backgroundColor( 0, 0 ), QColor( 25, 50, 75, 100 ) ); + QCOMPARE( table->backgroundColor( 0, 1 ), QColor( 30, 80, 90, 23 ) ); + QCOMPARE( table->backgroundColor( 0, 2 ), QColor( 25, 50, 75, 100 ) ); + QCOMPARE( table->backgroundColor( 0, 3 ), QColor( 30, 80, 90, 23 ) ); + QCOMPARE( table->backgroundColor( 1, 0 ), QColor( 25, 50, 75, 100 ) ); + QCOMPARE( table->backgroundColor( 1, 1 ), QColor( 30, 80, 90, 23 ) ); + QCOMPARE( table->backgroundColor( 1, 2 ), QColor( 25, 50, 75, 100 ) ); + QCOMPARE( table->backgroundColor( 1, 3 ), QColor( 30, 80, 90, 23 ) ); + style.cellBackgroundColor = QColor( 111, 112, 113, 114 ); + table->setCellStyle( QgsLayoutTable::OddRows, style ); + QCOMPARE( table->backgroundColor( 0, 0 ), QColor( 111, 112, 113, 114 ) ); + QCOMPARE( table->backgroundColor( 0, 1 ), QColor( 111, 112, 113, 114 ) ); + QCOMPARE( table->backgroundColor( 0, 2 ), QColor( 111, 112, 113, 114 ) ); + QCOMPARE( table->backgroundColor( 0, 3 ), QColor( 111, 112, 113, 114 ) ); + QCOMPARE( table->backgroundColor( 1, 0 ), QColor( 25, 50, 75, 100 ) ); + QCOMPARE( table->backgroundColor( 1, 1 ), QColor( 30, 80, 90, 23 ) ); + QCOMPARE( table->backgroundColor( 1, 2 ), QColor( 25, 50, 75, 100 ) ); + QCOMPARE( table->backgroundColor( 1, 3 ), QColor( 30, 80, 90, 23 ) ); + QCOMPARE( table->backgroundColor( 2, 0 ), QColor( 111, 112, 113, 114 ) ); + QCOMPARE( table->backgroundColor( 2, 1 ), QColor( 111, 112, 113, 114 ) ); + QCOMPARE( table->backgroundColor( 2, 2 ), QColor( 111, 112, 113, 114 ) ); + QCOMPARE( table->backgroundColor( 2, 3 ), QColor( 111, 112, 113, 114 ) ); + style.cellBackgroundColor = QColor( 222, 223, 224, 225 ); + table->setCellStyle( QgsLayoutTable::EvenRows, style ); + QCOMPARE( table->backgroundColor( 0, 0 ), QColor( 111, 112, 113, 114 ) ); + QCOMPARE( table->backgroundColor( 0, 1 ), QColor( 111, 112, 113, 114 ) ); + QCOMPARE( table->backgroundColor( 0, 2 ), QColor( 111, 112, 113, 114 ) ); + QCOMPARE( table->backgroundColor( 0, 3 ), QColor( 111, 112, 113, 114 ) ); + QCOMPARE( table->backgroundColor( 1, 0 ), QColor( 222, 223, 224, 225 ) ); + QCOMPARE( table->backgroundColor( 1, 1 ), QColor( 222, 223, 224, 225 ) ); + QCOMPARE( table->backgroundColor( 1, 2 ), QColor( 222, 223, 224, 225 ) ); + QCOMPARE( table->backgroundColor( 1, 3 ), QColor( 222, 223, 224, 225 ) ); + QCOMPARE( table->backgroundColor( 2, 0 ), QColor( 111, 112, 113, 114 ) ); + QCOMPARE( table->backgroundColor( 2, 1 ), QColor( 111, 112, 113, 114 ) ); + QCOMPARE( table->backgroundColor( 2, 2 ), QColor( 111, 112, 113, 114 ) ); + QCOMPARE( table->backgroundColor( 2, 3 ), QColor( 111, 112, 113, 114 ) ); + QCOMPARE( table->backgroundColor( 3, 0 ), QColor( 222, 223, 224, 225 ) ); + QCOMPARE( table->backgroundColor( 3, 1 ), QColor( 222, 223, 224, 225 ) ); + QCOMPARE( table->backgroundColor( 3, 2 ), QColor( 222, 223, 224, 225 ) ); + QCOMPARE( table->backgroundColor( 3, 3 ), QColor( 222, 223, 224, 225 ) ); + style.cellBackgroundColor = QColor( 1, 2, 3, 4 ); + table->setCellStyle( QgsLayoutTable::FirstColumn, style ); + QCOMPARE( table->backgroundColor( 0, 0 ), QColor( 1, 2, 3, 4 ) ); + QCOMPARE( table->backgroundColor( 0, 1 ), QColor( 111, 112, 113, 114 ) ); + QCOMPARE( table->backgroundColor( 0, 2 ), QColor( 111, 112, 113, 114 ) ); + QCOMPARE( table->backgroundColor( 0, 3 ), QColor( 111, 112, 113, 114 ) ); + QCOMPARE( table->backgroundColor( 1, 0 ), QColor( 1, 2, 3, 4 ) ); + QCOMPARE( table->backgroundColor( 1, 1 ), QColor( 222, 223, 224, 225 ) ); + QCOMPARE( table->backgroundColor( 1, 2 ), QColor( 222, 223, 224, 225 ) ); + QCOMPARE( table->backgroundColor( 1, 3 ), QColor( 222, 223, 224, 225 ) ); + QCOMPARE( table->backgroundColor( 2, 0 ), QColor( 1, 2, 3, 4 ) ); + style.cellBackgroundColor = QColor( 7, 8, 9, 10 ); + table->setCellStyle( QgsLayoutTable::LastColumn, style ); + QCOMPARE( table->backgroundColor( 0, 0 ), QColor( 1, 2, 3, 4 ) ); + QCOMPARE( table->backgroundColor( 0, 1 ), QColor( 111, 112, 113, 114 ) ); + QCOMPARE( table->backgroundColor( 0, 2 ), QColor( 111, 112, 113, 114 ) ); + QCOMPARE( table->backgroundColor( 0, 5 ), QColor( 7, 8, 9, 10 ) ); + QCOMPARE( table->backgroundColor( 1, 0 ), QColor( 1, 2, 3, 4 ) ); + QCOMPARE( table->backgroundColor( 1, 1 ), QColor( 222, 223, 224, 225 ) ); + QCOMPARE( table->backgroundColor( 1, 2 ), QColor( 222, 223, 224, 225 ) ); + QCOMPARE( table->backgroundColor( 1, 5 ), QColor( 7, 8, 9, 10 ) ); + QCOMPARE( table->backgroundColor( 2, 0 ), QColor( 1, 2, 3, 4 ) ); + QCOMPARE( table->backgroundColor( 2, 5 ), QColor( 7, 8, 9, 10 ) ); + style.cellBackgroundColor = QColor( 87, 88, 89, 90 ); + table->setCellStyle( QgsLayoutTable::HeaderRow, style ); + QCOMPARE( table->backgroundColor( -1, 0 ), QColor( 87, 88, 89, 90 ) ); + QCOMPARE( table->backgroundColor( -1, 1 ), QColor( 87, 88, 89, 90 ) ); + QCOMPARE( table->backgroundColor( -1, 2 ), QColor( 87, 88, 89, 90 ) ); + QCOMPARE( table->backgroundColor( -1, 5 ), QColor( 87, 88, 89, 90 ) ); + QCOMPARE( table->backgroundColor( 0, 0 ), QColor( 1, 2, 3, 4 ) ); + QCOMPARE( table->backgroundColor( 0, 1 ), QColor( 111, 112, 113, 114 ) ); + QCOMPARE( table->backgroundColor( 0, 2 ), QColor( 111, 112, 113, 114 ) ); + QCOMPARE( table->backgroundColor( 0, 5 ), QColor( 7, 8, 9, 10 ) ); + QCOMPARE( table->backgroundColor( 1, 0 ), QColor( 1, 2, 3, 4 ) ); + QCOMPARE( table->backgroundColor( 1, 1 ), QColor( 222, 223, 224, 225 ) ); + QCOMPARE( table->backgroundColor( 1, 2 ), QColor( 222, 223, 224, 225 ) ); + QCOMPARE( table->backgroundColor( 1, 5 ), QColor( 7, 8, 9, 10 ) ); + QCOMPARE( table->backgroundColor( 2, 0 ), QColor( 1, 2, 3, 4 ) ); + QCOMPARE( table->backgroundColor( 2, 5 ), QColor( 7, 8, 9, 10 ) ); + style.cellBackgroundColor = QColor( 187, 188, 189, 190 ); + table->setCellStyle( QgsLayoutTable::FirstRow, style ); + QCOMPARE( table->backgroundColor( -1, 0 ), QColor( 87, 88, 89, 90 ) ); + QCOMPARE( table->backgroundColor( -1, 1 ), QColor( 87, 88, 89, 90 ) ); + QCOMPARE( table->backgroundColor( -1, 2 ), QColor( 87, 88, 89, 90 ) ); + QCOMPARE( table->backgroundColor( -1, 5 ), QColor( 87, 88, 89, 90 ) ); + QCOMPARE( table->backgroundColor( 0, 0 ), QColor( 187, 188, 189, 190 ) ); + QCOMPARE( table->backgroundColor( 0, 1 ), QColor( 187, 188, 189, 190 ) ); + QCOMPARE( table->backgroundColor( 0, 2 ), QColor( 187, 188, 189, 190 ) ); + QCOMPARE( table->backgroundColor( 0, 5 ), QColor( 187, 188, 189, 190 ) ); + QCOMPARE( table->backgroundColor( 1, 0 ), QColor( 1, 2, 3, 4 ) ); + QCOMPARE( table->backgroundColor( 1, 1 ), QColor( 222, 223, 224, 225 ) ); + QCOMPARE( table->backgroundColor( 1, 2 ), QColor( 222, 223, 224, 225 ) ); + QCOMPARE( table->backgroundColor( 1, 5 ), QColor( 7, 8, 9, 10 ) ); + QCOMPARE( table->backgroundColor( 2, 0 ), QColor( 1, 2, 3, 4 ) ); + QCOMPARE( table->backgroundColor( 2, 5 ), QColor( 7, 8, 9, 10 ) ); + style.cellBackgroundColor = QColor( 147, 148, 149, 150 ); + table->setCellStyle( QgsLayoutTable::LastRow, style ); + QCOMPARE( table->backgroundColor( -1, 0 ), QColor( 87, 88, 89, 90 ) ); + QCOMPARE( table->backgroundColor( -1, 1 ), QColor( 87, 88, 89, 90 ) ); + QCOMPARE( table->backgroundColor( -1, 2 ), QColor( 87, 88, 89, 90 ) ); + QCOMPARE( table->backgroundColor( -1, 5 ), QColor( 87, 88, 89, 90 ) ); + QCOMPARE( table->backgroundColor( 0, 0 ), QColor( 187, 188, 189, 190 ) ); + QCOMPARE( table->backgroundColor( 0, 1 ), QColor( 187, 188, 189, 190 ) ); + QCOMPARE( table->backgroundColor( 0, 2 ), QColor( 187, 188, 189, 190 ) ); + QCOMPARE( table->backgroundColor( 0, 5 ), QColor( 187, 188, 189, 190 ) ); + QCOMPARE( table->backgroundColor( 1, 0 ), QColor( 1, 2, 3, 4 ) ); + QCOMPARE( table->backgroundColor( 1, 1 ), QColor( 222, 223, 224, 225 ) ); + QCOMPARE( table->backgroundColor( 1, 2 ), QColor( 222, 223, 224, 225 ) ); + QCOMPARE( table->backgroundColor( 1, 5 ), QColor( 7, 8, 9, 10 ) ); + QCOMPARE( table->backgroundColor( 2, 0 ), QColor( 1, 2, 3, 4 ) ); + QCOMPARE( table->backgroundColor( 2, 5 ), QColor( 7, 8, 9, 10 ) ); + QCOMPARE( table->backgroundColor( 9, 0 ), QColor( 147, 148, 149, 150 ) ); + QCOMPARE( table->backgroundColor( 9, 1 ), QColor( 147, 148, 149, 150 ) ); + QCOMPARE( table->backgroundColor( 9, 2 ), QColor( 147, 148, 149, 150 ) ); + QCOMPARE( table->backgroundColor( 9, 5 ), QColor( 147, 148, 149, 150 ) ); +} + +void TestQgsLayoutTable::cellStylesRender() +{ + QgsLayout l( QgsProject::instance() ); + l.initializeDefaults(); + QgsLayoutItemAttributeTable *table = new QgsLayoutItemAttributeTable( &l ); + QgsLayoutFrame *frame1 = new QgsLayoutFrame( &l, table ); + frame1->attemptSetSceneRect( QRectF( 5, 5, 100, 30 ) ); + QgsLayoutFrame *frame2 = new QgsLayoutFrame( &l, table ); + frame2->attemptSetSceneRect( QRectF( 5, 40, 100, 30 ) ); + frame1->setFrameEnabled( true ); + frame2->setFrameEnabled( true ); + table->addFrame( frame1 ); + table->addFrame( frame2 ); + table->setVectorLayer( mVectorLayer ); + table->setDisplayOnlyVisibleFeatures( false ); + table->setMaximumNumberOfFeatures( 10 ); + table->setContentFont( QgsFontUtils::getStandardTestFont() ); + table->setHeaderFont( QgsFontUtils::getStandardTestFont() ); + table->setBackgroundColor( Qt::yellow ); + + table->setMaximumNumberOfFeatures( 3 ); + table->setShowEmptyRows( true ); + + QgsLayoutTableStyle style; + style.enabled = true; + style.cellBackgroundColor = QColor( 25, 50, 75, 100 ); + table->setCellStyle( QgsLayoutTable::OddColumns, style ); + style.cellBackgroundColor = QColor( 90, 110, 150, 200 ); + table->setCellStyle( QgsLayoutTable::EvenRows, style ); + style.cellBackgroundColor = QColor( 150, 160, 210, 200 ); + table->setCellStyle( QgsLayoutTable::HeaderRow, style ); + style.cellBackgroundColor = QColor( 0, 200, 50, 200 ); + table->setCellStyle( QgsLayoutTable::FirstColumn, style ); + style.cellBackgroundColor = QColor( 200, 50, 0, 200 ); + table->setCellStyle( QgsLayoutTable::LastColumn, style ); + style.cellBackgroundColor = QColor( 200, 50, 200, 200 ); + table->setCellStyle( QgsLayoutTable::FirstRow, style ); + style.cellBackgroundColor = QColor( 50, 200, 200, 200 ); + table->setCellStyle( QgsLayoutTable::LastRow, style ); + + QgsLayoutChecker checker( QStringLiteral( "composerattributetable_cellstyle" ), &l ); + checker.setColorTolerance( 10 ); + checker.setControlPathPrefix( QStringLiteral( "composer_table" ) ); + QVERIFY( checker.testLayout( mReport, 0 ) ); +} + +QGSTEST_MAIN( TestQgsLayoutTable ) +#include "testqgslayouttable.moc" diff --git a/tests/testdata/control_images/composer_table/expected_composerattributetable_align/expected_composerattributetable_align_mask.png b/tests/testdata/control_images/composer_table/expected_composerattributetable_align/expected_composerattributetable_align_mask.png index c466948df33..1f9670f456b 100755 Binary files a/tests/testdata/control_images/composer_table/expected_composerattributetable_align/expected_composerattributetable_align_mask.png and b/tests/testdata/control_images/composer_table/expected_composerattributetable_align/expected_composerattributetable_align_mask.png differ diff --git a/tests/testdata/control_images/composer_table/expected_composerattributetable_autowrap/expected_composerattributetable_autowrap_mask.png b/tests/testdata/control_images/composer_table/expected_composerattributetable_autowrap/expected_composerattributetable_autowrap_mask.png index 34659ec7c9b..6adb3a7fcf4 100644 Binary files a/tests/testdata/control_images/composer_table/expected_composerattributetable_autowrap/expected_composerattributetable_autowrap_mask.png and b/tests/testdata/control_images/composer_table/expected_composerattributetable_autowrap/expected_composerattributetable_autowrap_mask.png differ diff --git a/tests/testdata/control_images/composer_table/expected_composerattributetable_cellstyle/expected_composerattributetable_cellstyle_mask.png b/tests/testdata/control_images/composer_table/expected_composerattributetable_cellstyle/expected_composerattributetable_cellstyle_mask.png index 117abd6f079..5569e9987a1 100644 Binary files a/tests/testdata/control_images/composer_table/expected_composerattributetable_cellstyle/expected_composerattributetable_cellstyle_mask.png and b/tests/testdata/control_images/composer_table/expected_composerattributetable_cellstyle/expected_composerattributetable_cellstyle_mask.png differ diff --git a/tests/testdata/control_images/composer_table/expected_composerattributetable_columnwidth/expected_composerattributetable_columnwidth_mask.png b/tests/testdata/control_images/composer_table/expected_composerattributetable_columnwidth/expected_composerattributetable_columnwidth_mask.png index f81fc4b2e27..e320ff2fc7d 100644 Binary files a/tests/testdata/control_images/composer_table/expected_composerattributetable_columnwidth/expected_composerattributetable_columnwidth_mask.png and b/tests/testdata/control_images/composer_table/expected_composerattributetable_columnwidth/expected_composerattributetable_columnwidth_mask.png differ diff --git a/tests/testdata/control_images/composer_table/expected_composerattributetable_drawempty/expected_composerattributetable_drawempty_mask.png b/tests/testdata/control_images/composer_table/expected_composerattributetable_drawempty/expected_composerattributetable_drawempty_mask.png index bb9da9ae7f0..b9ed7227294 100644 Binary files a/tests/testdata/control_images/composer_table/expected_composerattributetable_drawempty/expected_composerattributetable_drawempty_mask.png and b/tests/testdata/control_images/composer_table/expected_composerattributetable_drawempty/expected_composerattributetable_drawempty_mask.png differ diff --git a/tests/testdata/control_images/composer_table/expected_composerattributetable_headersonly/expected_composerattributetable_headersonly_mask.png b/tests/testdata/control_images/composer_table/expected_composerattributetable_headersonly/expected_composerattributetable_headersonly_mask.png index 0b93efaa20c..3500ed3ff18 100644 Binary files a/tests/testdata/control_images/composer_table/expected_composerattributetable_headersonly/expected_composerattributetable_headersonly_mask.png and b/tests/testdata/control_images/composer_table/expected_composerattributetable_headersonly/expected_composerattributetable_headersonly_mask.png differ diff --git a/tests/testdata/control_images/composer_table/expected_composerattributetable_hidetable/expected_composerattributetable_hidetable_mask.png b/tests/testdata/control_images/composer_table/expected_composerattributetable_hidetable/expected_composerattributetable_hidetable_mask.png index 557505618ae..4a5f82fbc5c 100644 Binary files a/tests/testdata/control_images/composer_table/expected_composerattributetable_hidetable/expected_composerattributetable_hidetable_mask.png and b/tests/testdata/control_images/composer_table/expected_composerattributetable_hidetable/expected_composerattributetable_hidetable_mask.png differ diff --git a/tests/testdata/control_images/composer_table/expected_composerattributetable_horizontalgrid/expected_composerattributetable_horizontalgrid_mask.png b/tests/testdata/control_images/composer_table/expected_composerattributetable_horizontalgrid/expected_composerattributetable_horizontalgrid_mask.png index 466cdb25892..ecb302706ba 100644 Binary files a/tests/testdata/control_images/composer_table/expected_composerattributetable_horizontalgrid/expected_composerattributetable_horizontalgrid_mask.png and b/tests/testdata/control_images/composer_table/expected_composerattributetable_horizontalgrid/expected_composerattributetable_horizontalgrid_mask.png differ diff --git a/tests/testdata/control_images/composer_table/expected_composerattributetable_multiline/expected_composerattributetable_multiline_mask.png b/tests/testdata/control_images/composer_table/expected_composerattributetable_multiline/expected_composerattributetable_multiline_mask.png index b9fe73b0492..e2170547644 100644 Binary files a/tests/testdata/control_images/composer_table/expected_composerattributetable_multiline/expected_composerattributetable_multiline_mask.png and b/tests/testdata/control_images/composer_table/expected_composerattributetable_multiline/expected_composerattributetable_multiline_mask.png differ diff --git a/tests/testdata/control_images/composer_table/expected_composerattributetable_render/expected_composerattributetable_render_mask.png b/tests/testdata/control_images/composer_table/expected_composerattributetable_render/expected_composerattributetable_render_mask.png index 73b242e4d94..ad5fce3f79d 100644 Binary files a/tests/testdata/control_images/composer_table/expected_composerattributetable_render/expected_composerattributetable_render_mask.png and b/tests/testdata/control_images/composer_table/expected_composerattributetable_render/expected_composerattributetable_render_mask.png differ diff --git a/tests/testdata/control_images/composer_table/expected_composerattributetable_showmessage/expected_composerattributetable_showmessage_mask.png b/tests/testdata/control_images/composer_table/expected_composerattributetable_showmessage/expected_composerattributetable_showmessage_mask.png index 109b5bdc8f6..cb0ef5141c7 100644 Binary files a/tests/testdata/control_images/composer_table/expected_composerattributetable_showmessage/expected_composerattributetable_showmessage_mask.png and b/tests/testdata/control_images/composer_table/expected_composerattributetable_showmessage/expected_composerattributetable_showmessage_mask.png differ diff --git a/tests/testdata/control_images/composer_table/expected_composerattributetable_verticalgrid/expected_composerattributetable_verticalgrid_mask.png b/tests/testdata/control_images/composer_table/expected_composerattributetable_verticalgrid/expected_composerattributetable_verticalgrid_mask.png index 5cc5479901b..d8c26a598dd 100644 Binary files a/tests/testdata/control_images/composer_table/expected_composerattributetable_verticalgrid/expected_composerattributetable_verticalgrid_mask.png and b/tests/testdata/control_images/composer_table/expected_composerattributetable_verticalgrid/expected_composerattributetable_verticalgrid_mask.png differ