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