1 /***************************************************************************
2                              qgsreportsectionfieldgroup.cpp
3                              --------------------
4     begin                : December 2017
5     copyright            : (C) 2017 by Nyall Dawson
6     email                : nyall dot dawson at gmail dot com
7  ***************************************************************************/
8 /***************************************************************************
9  *                                                                         *
10  *   This program is free software; you can redistribute it and/or modify  *
11  *   it under the terms of the GNU General Public License as published by  *
12  *   the Free Software Foundation; either version 2 of the License, or     *
13  *   (at your option) any later version.                                   *
14  *                                                                         *
15  ***************************************************************************/
16 
17 #include "qgsreportsectionfieldgroup.h"
18 #include "qgslayout.h"
19 
20 ///@cond NOT_STABLE
21 
QgsReportSectionFieldGroup(QgsAbstractReportSection * parent)22 QgsReportSectionFieldGroup::QgsReportSectionFieldGroup( QgsAbstractReportSection *parent )
23   : QgsAbstractReportSection( parent )
24 {
25 
26 }
27 
description() const28 QString QgsReportSectionFieldGroup::description() const
29 {
30   if ( mCoverageLayer.get() )
31     return QObject::tr( "Group: %1 - %2" ).arg( mCoverageLayer->name(), mField );
32   else
33     return QObject::tr( "Group" );
34 }
35 
icon() const36 QIcon QgsReportSectionFieldGroup::icon() const
37 {
38   return QgsApplication::getThemeIcon( QStringLiteral( "/mIconFieldText.svg" ) );
39 }
40 
clone() const41 QgsReportSectionFieldGroup *QgsReportSectionFieldGroup::clone() const
42 {
43   std::unique_ptr< QgsReportSectionFieldGroup > copy = qgis::make_unique< QgsReportSectionFieldGroup >( nullptr );
44   copyCommonProperties( copy.get() );
45 
46   if ( mBody )
47   {
48     copy->mBody.reset( mBody->clone() );
49   }
50   else
51     copy->mBody.reset();
52 
53   copy->setLayer( mCoverageLayer.get() );
54   copy->setField( mField );
55   copy->setSortAscending( mSortAscending );
56   copy->setBodyEnabled( mBodyEnabled );
57 
58   return copy.release();
59 }
60 
beginRender()61 bool QgsReportSectionFieldGroup::beginRender()
62 {
63   if ( !mCoverageLayer.get() )
64     return false;
65 
66   if ( !mField.isEmpty() )
67   {
68     mFieldIndex = mCoverageLayer->fields().lookupField( mField );
69     if ( mFieldIndex < 0 )
70       return false;
71 
72     if ( mBody )
73       mBody->reportContext().setLayer( mCoverageLayer.get() );
74 
75     mFeatures = QgsFeatureIterator();
76   }
77   return QgsAbstractReportSection::beginRender();
78 }
79 
prepareHeader()80 bool QgsReportSectionFieldGroup::prepareHeader()
81 {
82   if ( !header() )
83     return false;
84 
85   if ( !mFeatures.isValid() )
86   {
87     mFeatures = mCoverageLayer->getFeatures( buildFeatureRequest() );
88   }
89 
90   mHeaderFeature = getNextFeature();
91   header()->reportContext().blockSignals( true );
92   header()->reportContext().setLayer( mCoverageLayer.get() );
93   header()->reportContext().blockSignals( false );
94   header()->reportContext().setFeature( mHeaderFeature );
95   mSkipNextRequest = true;
96   mNoFeatures = !mHeaderFeature.isValid();
97   return mHeaderVisibility == AlwaysInclude || !mNoFeatures;
98 }
99 
prepareFooter()100 bool QgsReportSectionFieldGroup::prepareFooter()
101 {
102   return mFooterVisibility == AlwaysInclude || !mNoFeatures;
103 }
104 
nextBody(bool & ok)105 QgsLayout *QgsReportSectionFieldGroup::nextBody( bool &ok )
106 {
107   if ( !mFeatures.isValid() )
108   {
109     mFeatures = mCoverageLayer->getFeatures( buildFeatureRequest() );
110   }
111 
112   QgsFeature f;
113   if ( !mSkipNextRequest )
114   {
115     f = getNextFeature();
116   }
117   else
118   {
119     f = mHeaderFeature;
120     mSkipNextRequest = false;
121   }
122 
123   if ( !f.isValid() )
124   {
125     // no features left for this iteration
126     mFeatures = QgsFeatureIterator();
127 
128     if ( auto *lFooter = footer() )
129     {
130       lFooter->reportContext().blockSignals( true );
131       lFooter->reportContext().setLayer( mCoverageLayer.get() );
132       lFooter->reportContext().blockSignals( false );
133       lFooter->reportContext().setFeature( mLastFeature );
134     }
135     ok = false;
136     return nullptr;
137   }
138 
139   mLastFeature = f;
140 
141   updateChildContexts( f );
142 
143   ok = true;
144   if ( mBody && mBodyEnabled )
145   {
146     mBody->reportContext().blockSignals( true );
147     mBody->reportContext().setLayer( mCoverageLayer.get() );
148     mBody->reportContext().blockSignals( false );
149     mBody->reportContext().setFeature( f );
150   }
151 
152   return mBodyEnabled ? mBody.get() : nullptr;
153 }
154 
reset()155 void QgsReportSectionFieldGroup::reset()
156 {
157   QgsAbstractReportSection::reset();
158   mEncounteredValues.clear();
159   mSkipNextRequest = false;
160   mHeaderFeature = QgsFeature();
161   mLastFeature = QgsFeature();
162   mFeatures = QgsFeatureIterator();
163   mNoFeatures = false;
164 }
165 
setParentSection(QgsAbstractReportSection * parent)166 void QgsReportSectionFieldGroup::setParentSection( QgsAbstractReportSection *parent )
167 {
168   QgsAbstractReportSection::setParentSection( parent );
169   if ( !mCoverageLayer )
170     mCoverageLayer.resolveWeakly( project() );
171 }
172 
reloadSettings()173 void QgsReportSectionFieldGroup::reloadSettings()
174 {
175   QgsAbstractReportSection::reloadSettings();
176   if ( mBody )
177     mBody->reloadSettings();
178 }
179 
writePropertiesToElement(QDomElement & element,QDomDocument & doc,const QgsReadWriteContext & context) const180 bool QgsReportSectionFieldGroup::writePropertiesToElement( QDomElement &element, QDomDocument &doc, const QgsReadWriteContext &context ) const
181 {
182   element.setAttribute( QStringLiteral( "headerVisibility" ), static_cast< int >( mHeaderVisibility ) );
183   element.setAttribute( QStringLiteral( "footerVisibility" ), static_cast< int >( mFooterVisibility ) );
184   element.setAttribute( QStringLiteral( "field" ), mField );
185   element.setAttribute( QStringLiteral( "ascending" ), mSortAscending ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
186   element.setAttribute( QStringLiteral( "bodyEnabled" ), mBodyEnabled ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
187   if ( mCoverageLayer )
188   {
189     element.setAttribute( QStringLiteral( "coverageLayer" ), mCoverageLayer.layerId );
190     element.setAttribute( QStringLiteral( "coverageLayerName" ), mCoverageLayer.name );
191     element.setAttribute( QStringLiteral( "coverageLayerSource" ), mCoverageLayer.source );
192     element.setAttribute( QStringLiteral( "coverageLayerProvider" ), mCoverageLayer.provider );
193   }
194 
195   if ( mBody )
196   {
197     QDomElement bodyElement = doc.createElement( QStringLiteral( "body" ) );
198     bodyElement.appendChild( mBody->writeXml( doc, context ) );
199     element.appendChild( bodyElement );
200   }
201   return true;
202 }
203 
readPropertiesFromElement(const QDomElement & element,const QDomDocument & doc,const QgsReadWriteContext & context)204 bool QgsReportSectionFieldGroup::readPropertiesFromElement( const QDomElement &element, const QDomDocument &doc, const QgsReadWriteContext &context )
205 {
206   mHeaderVisibility = static_cast< SectionVisibility >( element.attribute( QStringLiteral( "headerVisibility" ) ).toInt() );
207   mFooterVisibility = static_cast< SectionVisibility >( element.attribute( QStringLiteral( "footerVisibility" ) ).toInt() );
208   mField = element.attribute( QStringLiteral( "field" ) );
209   mSortAscending = element.attribute( QStringLiteral( "ascending" ) ).toInt();
210   mBodyEnabled = element.attribute( QStringLiteral( "bodyEnabled" ) ).toInt();
211   QString layerId = element.attribute( QStringLiteral( "coverageLayer" ) );
212   QString layerName = element.attribute( QStringLiteral( "coverageLayerName" ) );
213   QString layerSource = element.attribute( QStringLiteral( "coverageLayerSource" ) );
214   QString layerProvider = element.attribute( QStringLiteral( "coverageLayerProvider" ) );
215   mCoverageLayer = QgsVectorLayerRef( layerId, layerName, layerSource, layerProvider );
216   mCoverageLayer.resolveWeakly( project() );
217 
218   const QDomElement bodyElement = element.firstChildElement( QStringLiteral( "body" ) );
219   if ( !bodyElement.isNull() )
220   {
221     const QDomElement bodyLayoutElem = bodyElement.firstChild().toElement();
222     std::unique_ptr< QgsLayout > body = qgis::make_unique< QgsLayout >( project() );
223     body->readXml( bodyLayoutElem, doc, context );
224     mBody = std::move( body );
225   }
226   return true;
227 }
228 
sortAscending() const229 bool QgsReportSectionFieldGroup::sortAscending() const
230 {
231   return mSortAscending;
232 }
233 
setSortAscending(bool sortAscending)234 void QgsReportSectionFieldGroup::setSortAscending( bool sortAscending )
235 {
236   mSortAscending = sortAscending;
237 }
238 
buildFeatureRequest() const239 QgsFeatureRequest QgsReportSectionFieldGroup::buildFeatureRequest() const
240 {
241   QgsFeatureRequest request;
242   QVariantMap filter = context().fieldFilters;
243 
244   QStringList filterParts;
245   for ( auto filterIt = filter.constBegin(); filterIt != filter.constEnd(); ++filterIt )
246   {
247     // use lookupField since we don't want case sensitivity
248     int fieldIndex = mCoverageLayer->fields().lookupField( filterIt.key() );
249     if ( fieldIndex >= 0 )
250     {
251       // layer has a matching field, so we need to filter by it
252       filterParts << QgsExpression::createFieldEqualityExpression( mCoverageLayer->fields().at( fieldIndex ).name(), filterIt.value() );
253     }
254   }
255   if ( !filterParts.empty() )
256   {
257     QString filterString = QStringLiteral( "(%1)" ).arg( filterParts.join( QLatin1String( ") AND (" ) ) );
258     request.setFilterExpression( filterString );
259   }
260 
261   request.addOrderBy( mField, mSortAscending );
262   return request;
263 }
264 
getNextFeature()265 QgsFeature QgsReportSectionFieldGroup::getNextFeature()
266 {
267   QgsFeature f;
268   QVariant currentValue;
269   bool first = true;
270   while ( first || ( ( !mBody || !mBodyEnabled ) && mEncounteredValues.contains( currentValue ) ) )
271   {
272     if ( !mFeatures.nextFeature( f ) )
273     {
274       return QgsFeature();
275     }
276 
277     first = false;
278     currentValue = f.attribute( mFieldIndex );
279   }
280 
281   mEncounteredValues.insert( currentValue );
282   return f;
283 }
284 
updateChildContexts(const QgsFeature & feature)285 void QgsReportSectionFieldGroup::updateChildContexts( const QgsFeature &feature )
286 {
287   QgsReportSectionContext c = context();
288   c.feature = feature;
289   if ( mCoverageLayer )
290     c.currentLayer = mCoverageLayer.get();
291 
292   QVariantMap currentFilter = c.fieldFilters;
293   currentFilter.insert( mField, feature.attribute( mFieldIndex ) );
294   c.fieldFilters = currentFilter;
295 
296   const QList< QgsAbstractReportSection * > sections = childSections();
297   for ( QgsAbstractReportSection *section : qgis::as_const( sections ) )
298   {
299     section->setContext( c );
300   }
301 }
302 
303 ///@endcond
304 
305