Skip to content
Permalink
Browse files

[reports] Tweak sub-section logic for multi-layer reports

Instead of requiring a single layer for use with nested field group
sections, allow use of different layers. In this case the child
layers are filtered so that any fields with names matching their
parent groups are filtered to match the parent section's value.

Also only include headers and footers for child field group sections
if the child has matching features found.
  • Loading branch information
nyalldawson committed Jan 23, 2018
1 parent 8880861 commit c91fd5a067fe45e4e629039b91e83b4c3840d062
@@ -32,6 +32,7 @@ class QgsReportSectionContext

QgsVectorLayer *currentLayer;

QVariantMap fieldFilters;
};

class QgsAbstractReportSection : QgsAbstractLayoutIterator
@@ -122,16 +123,20 @@ Returns the associated project.
Resets the section, ready for a new iteration.
%End

virtual void prepareHeader();
virtual bool prepareHeader();
%Docstring
Called just before rendering the section's header.
Called just before rendering the section's header. Should return true if the header
is to be included for this section, or false to skip the header for the current
section.

.. seealso:: :py:func:`prepareFooter`
%End

virtual void prepareFooter();
virtual bool prepareFooter();
%Docstring
Called just before rendering the section's footer.
Called just before rendering the section's footer. Should return true if the footer
is to be included for this section, or false to skip the footerfor the current
section.

.. seealso:: :py:func:`prepareHeader`
%End
@@ -134,7 +134,9 @@ ascending, or false for descending sort.

virtual bool beginRender();

virtual void prepareHeader();
virtual bool prepareHeader();

virtual bool prepareFooter();

virtual QgsLayout *nextBody( bool &ok );

@@ -215,9 +215,11 @@ bool QgsAbstractReportSection::next()
// if we have a header, then the current section will be the header
if ( mHeaderEnabled && mHeader )
{
prepareHeader();
mCurrentLayout = mHeader.get();
return true;
if ( prepareHeader() )
{
mCurrentLayout = mHeader.get();
return true;
}
}

// but if not, then the current section is a body
@@ -296,9 +298,11 @@ bool QgsAbstractReportSection::next()
// if we have a footer, then the current section will be the footer
if ( mFooterEnabled && mFooter )
{
prepareFooter();
mCurrentLayout = mFooter.get();
return true;
if ( prepareFooter() )
{
mCurrentLayout = mFooter.get();
return true;
}
}

// if not, then we're all done
@@ -338,6 +342,16 @@ void QgsAbstractReportSection::reset()
}
}

bool QgsAbstractReportSection::prepareHeader()
{
return true;
}

bool QgsAbstractReportSection::prepareFooter()
{
return true;
}

void QgsAbstractReportSection::setHeader( QgsLayout *header )
{
mHeader.reset( header );
@@ -44,8 +44,8 @@ class CORE_EXPORT QgsReportSectionContext
//! Current coverage layer
QgsVectorLayer *currentLayer = nullptr;

//! Current layer filters
QMap< QgsVectorLayer *, QString > layerFilters SIP_SKIP;
//! Current field filters
QVariantMap fieldFilters;
};

/**
@@ -135,16 +135,20 @@ class CORE_EXPORT QgsAbstractReportSection : public QgsAbstractLayoutIterator
virtual void reset();

/**
* Called just before rendering the section's header.
* Called just before rendering the section's header. Should return true if the header
* is to be included for this section, or false to skip the header for the current
* section.
* \see prepareFooter()
*/
virtual void prepareHeader() {}
virtual bool prepareHeader();

/**
* Called just before rendering the section's footer.
* Called just before rendering the section's footer. Should return true if the footer
* is to be included for this section, or false to skip the footerfor the current
* section.
* \see prepareHeader()
*/
virtual void prepareFooter() {}
virtual bool prepareFooter();

/**
* Returns the next body layout to export, or a nullptr if
@@ -77,10 +77,10 @@ bool QgsReportSectionFieldGroup::beginRender()
return QgsAbstractReportSection::beginRender();
}

void QgsReportSectionFieldGroup::prepareHeader()
bool QgsReportSectionFieldGroup::prepareHeader()
{
if ( !header() )
return;
return false;

if ( !mFeatures.isValid() )
{
@@ -93,6 +93,13 @@ void QgsReportSectionFieldGroup::prepareHeader()
header()->reportContext().blockSignals( false );
header()->reportContext().setFeature( mHeaderFeature );
mSkipNextRequest = true;
mNoFeatures = !mHeaderFeature.isValid();
return !mNoFeatures;
}

bool QgsReportSectionFieldGroup::prepareFooter()
{
return !mNoFeatures;
}

QgsLayout *QgsReportSectionFieldGroup::nextBody( bool &ok )
@@ -153,6 +160,7 @@ void QgsReportSectionFieldGroup::reset()
mHeaderFeature = QgsFeature();
mLastFeature = QgsFeature();
mFeatures = QgsFeatureIterator();
mNoFeatures = false;
}

void QgsReportSectionFieldGroup::setParentSection( QgsAbstractReportSection *parent )
@@ -227,9 +235,25 @@ void QgsReportSectionFieldGroup::setSortAscending( bool sortAscending )
QgsFeatureRequest QgsReportSectionFieldGroup::buildFeatureRequest() const
{
QgsFeatureRequest request;
QString filter = context().layerFilters.value( mCoverageLayer.get() );
if ( !filter.isEmpty() )
request.setFilterExpression( filter );
QVariantMap filter = context().fieldFilters;

QStringList filterParts;
for ( auto filterIt = filter.constBegin(); filterIt != filter.constEnd(); ++filterIt )
{
// use lookupField since we don't want case sensitivity
int fieldIndex = mCoverageLayer->fields().lookupField( filterIt.key() );
if ( fieldIndex >= 0 )
{
// layer has a matching field, so we need to filter by it
filterParts << QgsExpression::createFieldEqualityExpression( mCoverageLayer->fields().at( fieldIndex ).name(), filterIt.value() );
}
}
if ( !filterParts.empty() )
{
QString filterString = QStringLiteral( "(%1)" ).arg( filterParts.join( QStringLiteral( ") AND (" ) ) );
request.setFilterExpression( filterString );
}

request.addOrderBy( mField, mSortAscending );
return request;
}
@@ -261,10 +285,9 @@ void QgsReportSectionFieldGroup::updateChildContexts( const QgsFeature &feature
if ( mCoverageLayer )
c.currentLayer = mCoverageLayer.get();

QString currentFilter = c.layerFilters.value( mCoverageLayer.get() );
QString thisFilter = QgsExpression::createFieldEqualityExpression( mField, feature.attribute( mFieldIndex ) );
QString newFilter = currentFilter.isEmpty() ? thisFilter : QStringLiteral( "(%1) AND (%2)" ).arg( currentFilter, thisFilter );
c.layerFilters[ mCoverageLayer.get() ] = newFilter;
QVariantMap currentFilter = c.fieldFilters;
currentFilter.insert( mField, feature.attribute( mFieldIndex ) );
c.fieldFilters = currentFilter;

const QList< QgsAbstractReportSection * > sections = childSections();
for ( QgsAbstractReportSection *section : qgis::as_const( sections ) )
@@ -122,8 +122,8 @@ class CORE_EXPORT QgsReportSectionFieldGroup : public QgsAbstractReportSection

QgsReportSectionFieldGroup *clone() const override SIP_FACTORY;
bool beginRender() override;
void prepareHeader() override;
//void prepareFooter() override;
bool prepareHeader() override;
bool prepareFooter() override;
QgsLayout *nextBody( bool &ok ) override;
void reset() override;
void setParentSection( QgsAbstractReportSection *parentSection ) override;
@@ -142,6 +142,7 @@ class CORE_EXPORT QgsReportSectionFieldGroup : public QgsAbstractReportSection
int mFieldIndex = -1;
QgsFeatureIterator mFeatures;
bool mSkipNextRequest = false;
bool mNoFeatures = false;
QgsFeature mHeaderFeature;
QgsFeature mLastFeature;
QSet< QVariant > mEncounteredValues;

0 comments on commit c91fd5a

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