QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
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  switch ( mNextSection )
252  {
253  case Header:
254  {
255  // regardless of whether we have a header or not, the next section will be the body
256  mNextSection = Body;
257 
258  // if we have a header, then the current section will be the header
259  if ( mHeaderEnabled && mHeader )
260  {
261  if ( prepareHeader() )
262  {
263  mCurrentLayout = mHeader.get();
264  return true;
265  }
266  }
267 
268  // but if not, then the current section is a body
269  mNextSection = Body;
271  }
272 
273  case Body:
274  {
275  mNextSection = Children;
276 
277  bool ok = false;
278  // if we have a next body available, use it
279  QgsLayout *body = nextBody( ok );
280  if ( body )
281  {
282  mNextChild = 0;
283  mCurrentLayout = body;
284  return true;
285  }
286 
288  }
289 
290  case Children:
291  {
292  bool bodiesAvailable = false;
293  do
294  {
295  // we iterate through all the section's children...
296  while ( mNextChild < mChildren.count() )
297  {
298  // ... staying on the current child only while it still has content for us
299  if ( mChildren.at( mNextChild )->next() )
300  {
301  mCurrentLayout = mChildren.at( mNextChild )->layout();
302  return true;
303  }
304  else
305  {
306  // no more content for this child, so move to next child
307  mNextChild++;
308  }
309  }
310 
311  // used up all the children
312  // if we have a next body available, use it
313  QgsLayout *body = nextBody( bodiesAvailable );
314  if ( bodiesAvailable )
315  {
316  mNextChild = 0;
317 
318  for ( QgsAbstractReportSection *section : qgis::as_const( mChildren ) )
319  {
320  section->reset();
321  }
322  }
323  if ( body )
324  {
325  mCurrentLayout = body;
326  return true;
327  }
328  }
329  while ( bodiesAvailable );
330 
331  // all children and bodies have spent their content, so move to the footer
332  mNextSection = Footer;
334  }
335 
336  case Footer:
337  {
338  // regardless of whether we have a footer or not, this is the last section
339  mNextSection = End;
340 
341  // if we have a footer, then the current section will be the footer
342  if ( mFooterEnabled && mFooter )
343  {
344  if ( prepareFooter() )
345  {
346  mCurrentLayout = mFooter.get();
347  return true;
348  }
349  }
350 
351  // if not, then we're all done
353  }
354 
355  case End:
356  break;
357  }
358 
359  mCurrentLayout = nullptr;
360  return false;
361 }
362 
363 bool QgsAbstractReportSection::endRender()
364 {
365  // reset this section
366  reset();
367 
368  // and all children too
369  bool result = true;
370  for ( QgsAbstractReportSection *child : qgis::as_const( mChildren ) )
371  {
372  result = result && child->endRender();
373  }
374  return result;
375 }
376 
377 void QgsAbstractReportSection::reset()
378 {
379  mCurrentLayout = nullptr;
380  mNextChild = 0;
381  mNextSection = Header;
382  for ( QgsAbstractReportSection *section : qgis::as_const( mChildren ) )
383  {
384  section->reset();
385  }
386 }
387 
388 bool QgsAbstractReportSection::prepareHeader()
389 {
390  return true;
391 }
392 
393 bool QgsAbstractReportSection::prepareFooter()
394 {
395  return true;
396 }
397 
398 void QgsAbstractReportSection::setHeader( QgsLayout *header )
399 {
400  mHeader.reset( header );
401 }
402 
403 void QgsAbstractReportSection::setFooter( QgsLayout *footer )
404 {
405  mFooter.reset( footer );
406 }
407 
408 int QgsAbstractReportSection::row() const
409 {
410  if ( mParent )
411  return mParent->childSections().indexOf( const_cast<QgsAbstractReportSection *>( this ) );
412 
413  return 0;
414 }
415 
416 QgsAbstractReportSection *QgsAbstractReportSection::childSection( int index )
417 {
418  return mChildren.value( index );
419 }
420 
421 void QgsAbstractReportSection::appendChild( QgsAbstractReportSection *section )
422 {
423  section->setParentSection( this );
424  mChildren.append( section );
425 }
426 
427 void QgsAbstractReportSection::insertChild( int index, QgsAbstractReportSection *section )
428 {
429  section->setParentSection( this );
430  index = std::max( 0, index );
431  index = std::min( index, mChildren.count() );
432  mChildren.insert( index, section );
433 }
434 
435 void QgsAbstractReportSection::removeChild( QgsAbstractReportSection *section )
436 {
437  mChildren.removeAll( section );
438  delete section;
439 }
440 
441 void QgsAbstractReportSection::removeChildAt( int index )
442 {
443  if ( index < 0 || index >= mChildren.count() )
444  return;
445 
446  QgsAbstractReportSection *section = mChildren.at( index );
447  removeChild( section );
448 }
449 
450 void QgsAbstractReportSection::copyCommonProperties( QgsAbstractReportSection *destination ) const
451 {
452  destination->mHeaderEnabled = mHeaderEnabled;
453  if ( mHeader )
454  destination->mHeader.reset( mHeader->clone() );
455  else
456  destination->mHeader.reset();
457 
458  destination->mFooterEnabled = mFooterEnabled;
459  if ( mFooter )
460  destination->mFooter.reset( mFooter->clone() );
461  else
462  destination->mFooter.reset();
463 
464  qDeleteAll( destination->mChildren );
465  destination->mChildren.clear();
466 
467  for ( QgsAbstractReportSection *child : qgis::as_const( mChildren ) )
468  {
469  destination->appendChild( child->clone() );
470  }
471 }
472 
473 bool QgsAbstractReportSection::writePropertiesToElement( QDomElement &, QDomDocument &, const QgsReadWriteContext & ) const
474 {
475  return true;
476 }
477 
478 bool QgsAbstractReportSection::readPropertiesFromElement( const QDomElement &, const QDomDocument &, const QgsReadWriteContext & )
479 {
480  return true;
481 }
482 
484 
The class is used as a container of context for various read/write operations on other objects...
An interface for classes which can visit style entity (e.g.
#define FALLTHROUGH
Definition: qgis.h:681
virtual bool visitExit(const QgsStyleEntityVisitorInterface::Node &node)
Called when the visitor stops visiting a node.
void setLayer(QgsVectorLayer *layer)
Sets the vector layer associated with the layout&#39;s context.
Contains information relating to a node (i.e.
virtual QgsLayout * layout()=0
Returns the layout associated with the iterator.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts, annotations, canvases, etc.
Definition: qgsproject.h:89
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:49
QgsLayoutReportContext & reportContext()
Returns a reference to the layout&#39;s report context, which stores information relating to the current ...
Definition: qgslayout.cpp:368
virtual bool visitEnter(const QgsStyleEntityVisitorInterface::Node &node)
Called when the visitor starts visiting a node.
void setFeature(const QgsFeature &feature)
Sets the current feature for evaluating the layout.