QGIS API Documentation  3.14.0-Pi (9f7028fd23)
qgsabstractreportsection.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsabstractreportsection.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 
18 #include "qgslayout.h"
19 #include "qgsreport.h"
21 #include "qgsreportsectionlayout.h"
22 #include "qgsvectorlayer.h"
23 #include "qgsstyleentityvisitor.h"
24 
26 
27 QgsAbstractReportSection::QgsAbstractReportSection( QgsAbstractReportSection *parent )
28  : mParent( parent )
29 {}
30 
31 QgsAbstractReportSection::~QgsAbstractReportSection()
32 {
33  qDeleteAll( mChildren );
34 }
35 
36 QgsProject *QgsAbstractReportSection::project()
37 {
38  if ( QgsReport *report = dynamic_cast< QgsReport * >( this ) )
39  return report->layoutProject();
40 
41  QgsAbstractReportSection *current = this;
42  while ( QgsAbstractReportSection *parent = current->parentSection() )
43  {
44  if ( QgsReport *report = dynamic_cast< QgsReport * >( parent ) )
45  return report->layoutProject();
46 
47  current = parent;
48  }
49  return nullptr;
50 }
51 
52 void QgsAbstractReportSection::setContext( const QgsReportSectionContext &context )
53 {
54  auto setReportContext = [&context]( QgsLayout * layout )
55  {
56  if ( context.currentLayer )
57  {
58  layout->reportContext().blockSignals( true );
59  layout->reportContext().setLayer( context.currentLayer );
60  layout->reportContext().blockSignals( false );
61  }
62  layout->reportContext().setFeature( context.feature );
63  };
64 
65  mContext = context;
66  if ( mHeader )
67  setReportContext( mHeader.get() );
68  if ( mFooter )
69  setReportContext( mFooter.get() );
70 
71  for ( QgsAbstractReportSection *section : qgis::as_const( mChildren ) )
72  {
73  section->setContext( mContext );
74  }
75 }
76 
77 bool QgsAbstractReportSection::writeXml( QDomElement &parentElement, QDomDocument &doc, const QgsReadWriteContext &context ) const
78 {
79  QDomElement element = doc.createElement( QStringLiteral( "Section" ) );
80  element.setAttribute( QStringLiteral( "type" ), type() );
81 
82  element.setAttribute( QStringLiteral( "headerEnabled" ), mHeaderEnabled ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
83  if ( mHeader )
84  {
85  QDomElement headerElement = doc.createElement( QStringLiteral( "header" ) );
86  headerElement.appendChild( mHeader->writeXml( doc, context ) );
87  element.appendChild( headerElement );
88  }
89  element.setAttribute( QStringLiteral( "footerEnabled" ), mFooterEnabled ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
90  if ( mFooter )
91  {
92  QDomElement footerElement = doc.createElement( QStringLiteral( "footer" ) );
93  footerElement.appendChild( mFooter->writeXml( doc, context ) );
94  element.appendChild( footerElement );
95  }
96 
97  for ( QgsAbstractReportSection *section : mChildren )
98  {
99  section->writeXml( element, doc, context );
100  }
101 
102  writePropertiesToElement( element, doc, context );
103 
104  parentElement.appendChild( element );
105  return true;
106 }
107 
108 bool QgsAbstractReportSection::readXml( const QDomElement &element, const QDomDocument &doc, const QgsReadWriteContext &context )
109 {
110  if ( element.nodeName() != QStringLiteral( "Section" ) )
111  {
112  return false;
113  }
114 
115  mHeaderEnabled = element.attribute( QStringLiteral( "headerEnabled" ), QStringLiteral( "0" ) ).toInt();
116  mFooterEnabled = element.attribute( QStringLiteral( "footerEnabled" ), QStringLiteral( "0" ) ).toInt();
117  const QDomElement headerElement = element.firstChildElement( QStringLiteral( "header" ) );
118  if ( !headerElement.isNull() )
119  {
120  const QDomElement headerLayoutElem = headerElement.firstChild().toElement();
121  std::unique_ptr< QgsLayout > header = qgis::make_unique< QgsLayout >( project() );
122  header->readXml( headerLayoutElem, doc, context );
123  mHeader = std::move( header );
124  }
125  const QDomElement footerElement = element.firstChildElement( QStringLiteral( "footer" ) );
126  if ( !footerElement.isNull() )
127  {
128  const QDomElement footerLayoutElem = footerElement.firstChild().toElement();
129  std::unique_ptr< QgsLayout > footer = qgis::make_unique< QgsLayout >( project() );
130  footer->readXml( footerLayoutElem, doc, context );
131  mFooter = std::move( footer );
132  }
133 
134  const QDomNodeList sectionItemList = element.childNodes();
135  for ( int i = 0; i < sectionItemList.size(); ++i )
136  {
137  const QDomElement currentSectionElem = sectionItemList.at( i ).toElement();
138  if ( currentSectionElem.nodeName() != QStringLiteral( "Section" ) )
139  continue;
140 
141  const QString sectionType = currentSectionElem.attribute( QStringLiteral( "type" ) );
142 
143  //TODO - eventually move this to a registry when there's enough subclasses to warrant it
144  std::unique_ptr< QgsAbstractReportSection > section;
145  if ( sectionType == QLatin1String( "SectionFieldGroup" ) )
146  {
147  section = qgis::make_unique< QgsReportSectionFieldGroup >();
148  }
149  else if ( sectionType == QLatin1String( "SectionLayout" ) )
150  {
151  section = qgis::make_unique< QgsReportSectionLayout >();
152  }
153 
154  if ( section )
155  {
156  appendChild( section.get() );
157  section->readXml( currentSectionElem, doc, context );
158  ( void )section.release(); //ownership was transferred already
159  }
160  }
161 
162  bool result = readPropertiesFromElement( element, doc, context );
163  return result;
164 }
165 
166 void QgsAbstractReportSection::reloadSettings()
167 {
168  if ( mHeader )
169  mHeader->reloadSettings();
170  if ( mFooter )
171  mFooter->reloadSettings();
172 }
173 
174 bool QgsAbstractReportSection::accept( QgsStyleEntityVisitorInterface *visitor ) const
175 {
176  // NOTE: if visitEnter returns false it means "don't visit the report section", not "abort all further visitations"
177  if ( mParent && !visitor->visitEnter( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::ReportSection, QStringLiteral( "reportsection" ), QObject::tr( "Report Section" ) ) ) )
178  return true;
179 
180  if ( mHeader )
181  {
182  // NOTE: if visitEnter returns false it means "don't visit the header", not "abort all further visitations"
183  if ( visitor->visitEnter( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::ReportHeader, QStringLiteral( "reportheader" ), QObject::tr( "Report Header" ) ) ) )
184  {
185  if ( !mHeader->accept( visitor ) )
186  return false;
187 
188  if ( !visitor->visitExit( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::ReportHeader, QStringLiteral( "reportheader" ), QObject::tr( "Report Header" ) ) ) )
189  return false;
190  }
191  }
192 
193  for ( const QgsAbstractReportSection *child : mChildren )
194  {
195  if ( !child->accept( visitor ) )
196  return false;
197  }
198 
199  if ( mFooter )
200  {
201  // NOTE: if visitEnter returns false it means "don't visit the footer", not "abort all further visitations"
202  if ( visitor->visitEnter( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::ReportFooter, QStringLiteral( "reportfooter" ), QObject::tr( "Report Footer" ) ) ) )
203  {
204  if ( !mFooter->accept( visitor ) )
205  return false;
206 
207  if ( !visitor->visitExit( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::ReportFooter, QStringLiteral( "reportfooter" ), QObject::tr( "Report Footer" ) ) ) )
208  return false;
209  }
210  }
211 
212  if ( mParent && !visitor->visitExit( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::ReportSection, QStringLiteral( "reportsection" ), QObject::tr( "Report Section" ) ) ) )
213  return false;
214 
215  return true;
216 }
217 
218 QString QgsAbstractReportSection::filePath( const QString &baseFilePath, const QString &extension )
219 {
220  QString base = QStringLiteral( "%1_%2" ).arg( baseFilePath ).arg( mSectionNumber, 4, 10, QChar( '0' ) );
221  if ( !extension.startsWith( '.' ) )
222  base += '.';
223  base += extension;
224  return base;
225 }
226 
227 QgsLayout *QgsAbstractReportSection::layout()
228 {
229  return mCurrentLayout;
230 }
231 
232 bool QgsAbstractReportSection::beginRender()
233 {
234  // reset this section
235  reset();
236  mSectionNumber = 0;
237 
238  // and all children too
239  bool result = true;
240  for ( QgsAbstractReportSection *child : qgis::as_const( mChildren ) )
241  {
242  result = result && child->beginRender();
243  }
244  return result;
245 }
246 
247 bool QgsAbstractReportSection::next()
248 {
249  mSectionNumber++;
250 
251  if ( mNextSection == Header )
252  {
253  // regardless of whether we have a header or not, the next section will be the body
254  mNextSection = Body;
255 
256  // if we have a header, then the current section will be the header
257  if ( mHeaderEnabled && mHeader )
258  {
259  if ( prepareHeader() )
260  {
261  mCurrentLayout = mHeader.get();
262  return true;
263  }
264  }
265 
266  // but if not, then the current section is a body
267  mNextSection = Body;
268  }
269 
270  if ( mNextSection == Body )
271  {
272  mNextSection = Children;
273 
274  bool ok = false;
275  // if we have a next body available, use it
276  QgsLayout *body = nextBody( ok );
277  if ( body )
278  {
279  mNextChild = 0;
280  mCurrentLayout = body;
281  return true;
282  }
283  }
284 
285  if ( mNextSection == Children )
286  {
287  bool bodiesAvailable = false;
288  do
289  {
290  // we iterate through all the section's children...
291  while ( mNextChild < mChildren.count() )
292  {
293  // ... staying on the current child only while it still has content for us
294  if ( mChildren.at( mNextChild )->next() )
295  {
296  mCurrentLayout = mChildren.at( mNextChild )->layout();
297  return true;
298  }
299  else
300  {
301  // no more content for this child, so move to next child
302  mNextChild++;
303  }
304  }
305 
306  // used up all the children
307  // if we have a next body available, use it
308  QgsLayout *body = nextBody( bodiesAvailable );
309  if ( bodiesAvailable )
310  {
311  mNextChild = 0;
312 
313  for ( QgsAbstractReportSection *section : qgis::as_const( mChildren ) )
314  {
315  section->reset();
316  }
317  }
318  if ( body )
319  {
320  mCurrentLayout = body;
321  return true;
322  }
323  }
324  while ( bodiesAvailable );
325 
326  // all children and bodies have spent their content, so move to the footer
327  mNextSection = Footer;
328  }
329 
330  if ( mNextSection == Footer )
331  {
332  // regardless of whether we have a footer or not, this is the last section
333  mNextSection = End;
334 
335  // if we have a footer, then the current section will be the footer
336  if ( mFooterEnabled && mFooter )
337  {
338  if ( prepareFooter() )
339  {
340  mCurrentLayout = mFooter.get();
341  return true;
342  }
343  }
344 
345  // if not, then we're all done
346  }
347 
348  mCurrentLayout = nullptr;
349  return false;
350 }
351 
352 bool QgsAbstractReportSection::endRender()
353 {
354  // reset this section
355  reset();
356 
357  // and all children too
358  bool result = true;
359  for ( QgsAbstractReportSection *child : qgis::as_const( mChildren ) )
360  {
361  result = result && child->endRender();
362  }
363  return result;
364 }
365 
366 void QgsAbstractReportSection::reset()
367 {
368  mCurrentLayout = nullptr;
369  mNextChild = 0;
370  mNextSection = Header;
371  for ( QgsAbstractReportSection *section : qgis::as_const( mChildren ) )
372  {
373  section->reset();
374  }
375 }
376 
377 bool QgsAbstractReportSection::prepareHeader()
378 {
379  return true;
380 }
381 
382 bool QgsAbstractReportSection::prepareFooter()
383 {
384  return true;
385 }
386 
387 void QgsAbstractReportSection::setHeader( QgsLayout *header )
388 {
389  mHeader.reset( header );
390 }
391 
392 void QgsAbstractReportSection::setFooter( QgsLayout *footer )
393 {
394  mFooter.reset( footer );
395 }
396 
397 int QgsAbstractReportSection::row() const
398 {
399  if ( mParent )
400  return mParent->childSections().indexOf( const_cast<QgsAbstractReportSection *>( this ) );
401 
402  return 0;
403 }
404 
405 QgsAbstractReportSection *QgsAbstractReportSection::childSection( int index )
406 {
407  return mChildren.value( index );
408 }
409 
410 void QgsAbstractReportSection::appendChild( QgsAbstractReportSection *section )
411 {
412  section->setParentSection( this );
413  mChildren.append( section );
414 }
415 
416 void QgsAbstractReportSection::insertChild( int index, QgsAbstractReportSection *section )
417 {
418  section->setParentSection( this );
419  index = std::max( 0, index );
420  index = std::min( index, mChildren.count() );
421  mChildren.insert( index, section );
422 }
423 
424 void QgsAbstractReportSection::removeChild( QgsAbstractReportSection *section )
425 {
426  mChildren.removeAll( section );
427  delete section;
428 }
429 
430 void QgsAbstractReportSection::removeChildAt( int index )
431 {
432  if ( index < 0 || index >= mChildren.count() )
433  return;
434 
435  QgsAbstractReportSection *section = mChildren.at( index );
436  removeChild( section );
437 }
438 
439 void QgsAbstractReportSection::copyCommonProperties( QgsAbstractReportSection *destination ) const
440 {
441  destination->mHeaderEnabled = mHeaderEnabled;
442  if ( mHeader )
443  destination->mHeader.reset( mHeader->clone() );
444  else
445  destination->mHeader.reset();
446 
447  destination->mFooterEnabled = mFooterEnabled;
448  if ( mFooter )
449  destination->mFooter.reset( mFooter->clone() );
450  else
451  destination->mFooter.reset();
452 
453  qDeleteAll( destination->mChildren );
454  destination->mChildren.clear();
455 
456  for ( QgsAbstractReportSection *child : qgis::as_const( mChildren ) )
457  {
458  destination->appendChild( child->clone() );
459  }
460 }
461 
462 bool QgsAbstractReportSection::writePropertiesToElement( QDomElement &, QDomDocument &, const QgsReadWriteContext & ) const
463 {
464  return true;
465 }
466 
467 bool QgsAbstractReportSection::readPropertiesFromElement( const QDomElement &, const QDomDocument &, const QgsReadWriteContext & )
468 {
469  return true;
470 }
471 
473 
qgsreportsectionfieldgroup.h
QgsReadWriteContext
Definition: qgsreadwritecontext.h:34
qgsabstractreportsection.h
QgsStyleEntityVisitorInterface
Definition: qgsstyleentityvisitor.h:33
QgsStyleEntityVisitorInterface::visitExit
virtual bool visitExit(const QgsStyleEntityVisitorInterface::Node &node)
Called when the visitor stops visiting a node.
Definition: qgsstyleentityvisitor.h:183
QgsStyleEntityVisitorInterface::NodeType::ReportHeader
@ ReportHeader
Report header section.
QgsProject
Definition: qgsproject.h:92
QgsStyleEntityVisitorInterface::NodeType::ReportFooter
@ ReportFooter
Report footer section.
qgsreport.h
qgsreportsectionlayout.h
QgsStyleEntityVisitorInterface::Node
Contains information relating to a node (i.e.
Definition: qgsstyleentityvisitor.h:110
qgslayout.h
qgsvectorlayer.h
QgsStyleEntityVisitorInterface::visitEnter
virtual bool visitEnter(const QgsStyleEntityVisitorInterface::Node &node)
Called when the visitor starts visiting a node.
Definition: qgsstyleentityvisitor.h:169
QgsLayout
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:49
QgsStyleEntityVisitorInterface::NodeType::ReportSection
@ ReportSection
Report sub section.
qgsstyleentityvisitor.h