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