QGIS API Documentation  3.21.0-Master (5b68dc587e)
qgslayoutmanager.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgslayoutmanager.cpp
3  --------------------
4  Date : January 2017
5  Copyright : (C) 2017 Nyall Dawson
6  Email : nyall dot dawson at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
16 #include "qgslayoutmanager.h"
17 #include "qgslayout.h"
18 #include "qgsproject.h"
19 #include "qgslogger.h"
20 #include "qgslayoutundostack.h"
21 #include "qgsprintlayout.h"
22 #include "qgsreport.h"
24 #include "qgsreadwritecontext.h"
25 #include "qgsstyleentityvisitor.h"
26 #include "qgsruntimeprofiler.h"
27 #include <QMessageBox>
28 
30  : QObject( project )
31  , mProject( project )
32 {
33 
34 }
35 
37 {
38  clear();
39 }
40 
42 {
43  if ( !layout || mLayouts.contains( layout ) )
44  return false;
45 
46  // check for duplicate name
47  for ( QgsMasterLayoutInterface *l : std::as_const( mLayouts ) )
48  {
49  if ( l->name() == layout->name() )
50  {
51  delete layout;
52  return false;
53  }
54  }
55 
56  // ugly, but unavoidable for interfaces...
57  if ( QgsPrintLayout *l = dynamic_cast< QgsPrintLayout * >( layout ) )
58  {
59  connect( l, &QgsPrintLayout::nameChanged, this, [this, l]( const QString & newName )
60  {
61  emit layoutRenamed( l, newName );
62  } );
63  }
64  else if ( QgsReport *r = dynamic_cast< QgsReport * >( layout ) )
65  {
66  connect( r, &QgsReport::nameChanged, this, [this, r]( const QString & newName )
67  {
68  emit layoutRenamed( r, newName );
69  } );
70  }
71 
72  emit layoutAboutToBeAdded( layout->name() );
73  mLayouts << layout;
74  emit layoutAdded( layout->name() );
75  mProject->setDirty( true );
76  return true;
77 }
78 
80 {
81  if ( !layout )
82  return false;
83 
84  if ( !mLayouts.contains( layout ) )
85  return false;
86 
87  QString name = layout->name();
88  emit layoutAboutToBeRemoved( name );
89  mLayouts.removeAll( layout );
90  delete layout;
91  emit layoutRemoved( name );
92  mProject->setDirty( true );
93  return true;
94 }
95 
97 {
98  const QList< QgsMasterLayoutInterface * > layouts = mLayouts;
100  {
101  removeLayout( l );
102  }
103 }
104 
105 QList<QgsMasterLayoutInterface *> QgsLayoutManager::layouts() const
106 {
107  return mLayouts;
108 }
109 
110 QList<QgsPrintLayout *> QgsLayoutManager::printLayouts() const
111 {
112  QList<QgsPrintLayout *> result;
113  const QList<QgsMasterLayoutInterface *> _layouts( mLayouts );
114  result.reserve( _layouts.size() );
115  for ( const auto &layout : _layouts )
116  {
117  QgsPrintLayout *_item( dynamic_cast<QgsPrintLayout *>( layout ) );
118  if ( _item )
119  result.push_back( _item );
120  }
121  return result;
122 }
123 
125 {
126  for ( QgsMasterLayoutInterface *l : mLayouts )
127  {
128  if ( l->name() == name )
129  return l;
130  }
131  return nullptr;
132 }
133 
134 bool QgsLayoutManager::readXml( const QDomElement &element, const QDomDocument &doc )
135 {
136  clear();
137 
138  QDomElement layoutsElem = element;
139  if ( element.tagName() != QLatin1String( "Layouts" ) )
140  {
141  layoutsElem = element.firstChildElement( QStringLiteral( "Layouts" ) );
142  }
143  if ( layoutsElem.isNull() )
144  {
145  // handle legacy projects
146  layoutsElem = doc.documentElement();
147  }
148 
149  //restore each composer
150  bool result = true;
151  QDomNodeList composerNodes = element.elementsByTagName( QStringLiteral( "Composer" ) );
152  QgsScopedRuntimeProfile profile( tr( "Loading QGIS 2.x compositions" ), QStringLiteral( "projectload" ) );
153  for ( int i = 0; i < composerNodes.size(); ++i )
154  {
155  // This legacy title is the Composer "title" (that can be overridden by the Composition "name")
156  QString legacyTitle = composerNodes.at( i ).toElement().attribute( QStringLiteral( "title" ) );
157  // Convert compositions to layouts
158  QDomNodeList compositionNodes = composerNodes.at( i ).toElement().elementsByTagName( QStringLiteral( "Composition" ) );
159  for ( int j = 0; j < compositionNodes.size(); ++j )
160  {
161  std::unique_ptr< QgsPrintLayout > l( QgsCompositionConverter::createLayoutFromCompositionXml( compositionNodes.at( j ).toElement(), mProject ) );
162  if ( l )
163  {
164  if ( l->name().isEmpty() )
165  l->setName( legacyTitle );
166 
167  // some 2.x projects could end in a state where they had duplicated layout names. This is strictly forbidden in 3.x
168  // so check for duplicate name in layouts already added
169  int id = 2;
170  bool isDuplicateName = false;
171  QString originalName = l->name();
172  do
173  {
174  isDuplicateName = false;
175  for ( QgsMasterLayoutInterface *layout : std::as_const( mLayouts ) )
176  {
177  if ( l->name() == layout->name() )
178  {
179  isDuplicateName = true;
180  break;
181  }
182  }
183  if ( isDuplicateName )
184  {
185  l->setName( QStringLiteral( "%1 %2" ).arg( originalName ).arg( id ) );
186  id++;
187  }
188  }
189  while ( isDuplicateName );
190 
191  bool added = addLayout( l.release() );
192  result = added && result;
193  }
194  }
195  }
196 
197  QgsReadWriteContext context;
198  context.setPathResolver( mProject->pathResolver() );
199 
200  profile.switchTask( tr( "Creating layouts" ) );
201 
202  // restore layouts
203  const QDomNodeList layoutNodes = layoutsElem.childNodes();
204  for ( int i = 0; i < layoutNodes.size(); ++i )
205  {
206  if ( layoutNodes.at( i ).nodeName() != QLatin1String( "Layout" ) )
207  continue;
208 
209  const QString layoutName = layoutNodes.at( i ).toElement().attribute( QStringLiteral( "name" ) );
210  QgsScopedRuntimeProfile profile( layoutName, QStringLiteral( "projectload" ) );
211 
212  std::unique_ptr< QgsPrintLayout > l = std::make_unique< QgsPrintLayout >( mProject );
213  l->undoStack()->blockCommands( true );
214  if ( !l->readLayoutXml( layoutNodes.at( i ).toElement(), doc, context ) )
215  {
216  result = false;
217  continue;
218  }
219  l->undoStack()->blockCommands( false );
220  if ( !addLayout( l.release() ) )
221  {
222  result = false;
223  }
224  }
225  //reports
226  profile.switchTask( tr( "Creating reports" ) );
227  const QDomNodeList reportNodes = element.elementsByTagName( QStringLiteral( "Report" ) );
228  for ( int i = 0; i < reportNodes.size(); ++i )
229  {
230  const QString layoutName = reportNodes.at( i ).toElement().attribute( QStringLiteral( "name" ) );
231  QgsScopedRuntimeProfile profile( layoutName, QStringLiteral( "projectload" ) );
232 
233  std::unique_ptr< QgsReport > r = std::make_unique< QgsReport >( mProject );
234  if ( !r->readLayoutXml( reportNodes.at( i ).toElement(), doc, context ) )
235  {
236  result = false;
237  continue;
238  }
239  if ( !addLayout( r.release() ) )
240  {
241  result = false;
242  }
243  }
244  return result;
245 }
246 
247 QDomElement QgsLayoutManager::writeXml( QDomDocument &doc ) const
248 {
249  QDomElement layoutsElem = doc.createElement( QStringLiteral( "Layouts" ) );
250 
251  QgsReadWriteContext context;
252  context.setPathResolver( mProject->pathResolver() );
253  for ( QgsMasterLayoutInterface *l : mLayouts )
254  {
255  QDomElement layoutElem = l->writeLayoutXml( doc, context );
256  layoutsElem.appendChild( layoutElem );
257  }
258  return layoutsElem;
259 }
260 
262 {
263  if ( !layout )
264  return nullptr;
265 
266  std::unique_ptr< QgsMasterLayoutInterface > newLayout( layout->clone() );
267  if ( !newLayout )
268  {
269  return nullptr;
270  }
271 
272  newLayout->setName( newName );
273  QgsMasterLayoutInterface *l = newLayout.get();
274  if ( !addLayout( newLayout.release() ) )
275  {
276  return nullptr;
277  }
278  else
279  {
280  return l;
281  }
282 }
283 
285 {
286  QStringList names;
287  names.reserve( mLayouts.size() );
288  for ( QgsMasterLayoutInterface *l : mLayouts )
289  {
290  names << l->name();
291  }
292  QString name;
293  int id = 1;
294  while ( name.isEmpty() || names.contains( name ) )
295  {
296  switch ( type )
297  {
299  name = tr( "Layout %1" ).arg( id );
300  break;
302  name = tr( "Report %1" ).arg( id );
303  break;
304  }
305  id++;
306  }
307  return name;
308 }
309 
311 {
312  if ( mLayouts.empty() )
313  return true;
314 
315  // NOTE: if visitEnter returns false it means "don't visit the layouts", not "abort all further visitations"
316  if ( !visitor->visitEnter( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::Layouts, QStringLiteral( "layouts" ), tr( "Layouts" ) ) ) )
317  return true;
318 
319  for ( QgsMasterLayoutInterface *l : mLayouts )
320  {
321  if ( !l->layoutAccept( visitor ) )
322  return false;
323  }
324 
325  if ( !visitor->visitExit( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::Layouts, QStringLiteral( "layouts" ), tr( "Layouts" ) ) ) )
326  return false;
327 
328  return true;
329 }
330 
331 
332 
333 //
334 // QgsLayoutManagerModel
335 //
336 
338  : QAbstractListModel( parent )
339  , mLayoutManager( manager )
340 {
341  connect( mLayoutManager, &QgsLayoutManager::layoutAboutToBeAdded, this, &QgsLayoutManagerModel::layoutAboutToBeAdded );
342  connect( mLayoutManager, &QgsLayoutManager::layoutAdded, this, &QgsLayoutManagerModel::layoutAdded );
343  connect( mLayoutManager, &QgsLayoutManager::layoutAboutToBeRemoved, this, &QgsLayoutManagerModel::layoutAboutToBeRemoved );
344  connect( mLayoutManager, &QgsLayoutManager::layoutRemoved, this, &QgsLayoutManagerModel::layoutRemoved );
345  connect( mLayoutManager, &QgsLayoutManager::layoutRenamed, this, &QgsLayoutManagerModel::layoutRenamed );
346 }
347 
348 int QgsLayoutManagerModel::rowCount( const QModelIndex &parent ) const
349 {
350  Q_UNUSED( parent )
351  return ( mLayoutManager ? mLayoutManager->layouts().count() : 0 ) + ( mAllowEmpty ? 1 : 0 );
352 }
353 
354 QVariant QgsLayoutManagerModel::data( const QModelIndex &index, int role ) const
355 {
356  if ( index.row() < 0 || index.row() >= rowCount( QModelIndex() ) )
357  return QVariant();
358 
359  const bool isEmpty = index.row() == 0 && mAllowEmpty;
360  const int layoutRow = mAllowEmpty ? index.row() - 1 : index.row();
361 
362  switch ( role )
363  {
364  case Qt::DisplayRole:
365  case Qt::ToolTipRole:
366  case Qt::EditRole:
367  return !isEmpty && mLayoutManager ? mLayoutManager->layouts().at( layoutRow )->name() : QVariant();
368 
369  case LayoutRole:
370  {
371  if ( isEmpty || !mLayoutManager )
372  return QVariant();
373  else if ( QgsLayout *l = dynamic_cast< QgsLayout * >( mLayoutManager->layouts().at( layoutRow ) ) )
374  return QVariant::fromValue( l );
375  else if ( QgsReport *r = dynamic_cast< QgsReport * >( mLayoutManager->layouts().at( layoutRow ) ) )
376  return QVariant::fromValue( r );
377  else
378  return QVariant();
379  }
380 
381  case Qt::DecorationRole:
382  {
383  return isEmpty || !mLayoutManager ? QIcon() : mLayoutManager->layouts().at( layoutRow )->icon();
384  }
385 
386  default:
387  return QVariant();
388  }
389 }
390 
391 bool QgsLayoutManagerModel::setData( const QModelIndex &index, const QVariant &value, int role )
392 {
393  if ( !index.isValid() || role != Qt::EditRole )
394  {
395  return false;
396  }
397  if ( index.row() >= mLayoutManager->layouts().count() )
398  {
399  return false;
400  }
401 
402  if ( index.row() == 0 && mAllowEmpty )
403  return false;
404 
405  if ( value.toString().isEmpty() )
406  return false;
407 
408  QgsMasterLayoutInterface *layout = layoutFromIndex( index );
409  if ( !layout )
410  return false;
411 
412  //has name changed?
413  bool changed = layout->name() != value.toString();
414  if ( !changed )
415  return true;
416 
417  //check if name already exists
418  QStringList layoutNames;
419  const QList< QgsMasterLayoutInterface * > layouts = QgsProject::instance()->layoutManager()->layouts();
420  for ( QgsMasterLayoutInterface *l : layouts )
421  {
422  layoutNames << l->name();
423  }
424  if ( layoutNames.contains( value.toString() ) )
425  {
426  //name exists!
427  QMessageBox::warning( nullptr, tr( "Rename Layout" ), tr( "There is already a layout named “%1”." ).arg( value.toString() ) );
428  return false;
429  }
430 
431  layout->setName( value.toString() );
432  return true;
433 }
434 
435 Qt::ItemFlags QgsLayoutManagerModel::flags( const QModelIndex &index ) const
436 {
437  Qt::ItemFlags flags = QAbstractListModel::flags( index );
438 #if 0 // double-click is now used for opening the layout
439  if ( index.isValid() )
440  {
441  return flags | Qt::ItemIsEditable;
442  }
443  else
444  {
445  return flags;
446  }
447 #endif
448  return flags;
449 }
450 
452 {
453  if ( index.row() == 0 && mAllowEmpty )
454  return nullptr;
455 
456  if ( QgsPrintLayout *l = qobject_cast< QgsPrintLayout * >( qvariant_cast<QObject *>( data( index, LayoutRole ) ) ) )
457  return l;
458  else if ( QgsReport *r = qobject_cast< QgsReport * >( qvariant_cast<QObject *>( data( index, LayoutRole ) ) ) )
459  return r;
460  else
461  return nullptr;
462 }
463 
465 {
466  if ( !mLayoutManager )
467  {
468  return QModelIndex();
469  }
470 
471  const int r = mLayoutManager->layouts().indexOf( layout );
472  if ( r < 0 )
473  return QModelIndex();
474 
475  QModelIndex idx = index( mAllowEmpty ? r + 1 : r, 0, QModelIndex() );
476  if ( idx.isValid() )
477  {
478  return idx;
479  }
480 
481  return QModelIndex();
482 }
483 
485 {
486  if ( allowEmpty == mAllowEmpty )
487  return;
488 
489  if ( allowEmpty )
490  {
491  beginInsertRows( QModelIndex(), 0, 0 );
492  mAllowEmpty = true;
493  endInsertRows();
494  }
495  else
496  {
497  beginRemoveRows( QModelIndex(), 0, 0 );
498  mAllowEmpty = false;
499  endRemoveRows();
500  }
501 }
502 
503 void QgsLayoutManagerModel::layoutAboutToBeAdded( const QString & )
504 {
505  int row = mLayoutManager->layouts().count() + ( mAllowEmpty ? 1 : 0 );
506  beginInsertRows( QModelIndex(), row, row );
507 }
508 
509 void QgsLayoutManagerModel::layoutAboutToBeRemoved( const QString &name )
510 {
511  QgsMasterLayoutInterface *l = mLayoutManager->layoutByName( name );
512  int row = mLayoutManager->layouts().indexOf( l ) + ( mAllowEmpty ? 1 : 0 );
513  if ( row >= 0 )
514  beginRemoveRows( QModelIndex(), row, row );
515 }
516 
517 void QgsLayoutManagerModel::layoutAdded( const QString & )
518 {
519  endInsertRows();
520 }
521 
522 void QgsLayoutManagerModel::layoutRemoved( const QString & )
523 {
524  endRemoveRows();
525 }
526 
527 void QgsLayoutManagerModel::layoutRenamed( QgsMasterLayoutInterface *layout, const QString & )
528 {
529  int row = mLayoutManager->layouts().indexOf( layout ) + ( mAllowEmpty ? 1 : 0 );
530  QModelIndex index = createIndex( row, 0 );
531  emit dataChanged( index, index, QVector<int>() << Qt::DisplayRole );
532 }
533 
534 //
535 // QgsLayoutManagerProxyModel
536 //
537 
539  : QSortFilterProxyModel( parent )
540 {
541  setDynamicSortFilter( true );
542  sort( 0 );
543  setSortCaseSensitivity( Qt::CaseInsensitive );
544 }
545 
546 bool QgsLayoutManagerProxyModel::lessThan( const QModelIndex &left, const QModelIndex &right ) const
547 {
548  const QString leftText = sourceModel()->data( left, Qt::DisplayRole ).toString();
549  const QString rightText = sourceModel()->data( right, Qt::DisplayRole ).toString();
550  if ( leftText.isEmpty() )
551  return true;
552  if ( rightText.isEmpty() )
553  return false;
554 
555  return QString::localeAwareCompare( leftText, rightText ) < 0;
556 }
557 
558 bool QgsLayoutManagerProxyModel::filterAcceptsRow( int sourceRow, const QModelIndex &sourceParent ) const
559 {
560  QgsLayoutManagerModel *model = qobject_cast< QgsLayoutManagerModel * >( sourceModel() );
561  if ( !model )
562  return false;
563 
564  QgsMasterLayoutInterface *layout = model->layoutFromIndex( model->index( sourceRow, 0, sourceParent ) );
565  if ( !layout )
566  return model->allowEmptyLayout();
567 
568  if ( !mFilterString.trimmed().isEmpty() )
569  {
570  if ( !layout->name().contains( mFilterString, Qt::CaseInsensitive ) )
571  return false;
572  }
573 
574  switch ( layout->layoutType() )
575  {
577  return mFilters & FilterPrintLayouts;
579  return mFilters & FilterReports;
580  }
581  return false;
582 }
583 
584 QgsLayoutManagerProxyModel::Filters QgsLayoutManagerProxyModel::filters() const
585 {
586  return mFilters;
587 }
588 
590 {
591  mFilters = filters;
592  invalidateFilter();
593 }
594 
595 void QgsLayoutManagerProxyModel::setFilterString( const QString &filter )
596 {
597  mFilterString = filter;
598  invalidateFilter();
599 }
static std::unique_ptr< QgsPrintLayout > createLayoutFromCompositionXml(const QDomElement &composerElement, QgsProject *project)
createLayoutFromCompositionXml is a factory that creates layout instances from a QGIS 2....
List model representing the print layouts and reports available in a layout manager.
bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole) override
QModelIndex indexFromLayout(QgsMasterLayoutInterface *layout) const
Returns the model index corresponding to a layout.
@ LayoutRole
Layout object.
Qt::ItemFlags flags(const QModelIndex &index) const override
QgsMasterLayoutInterface * layoutFromIndex(const QModelIndex &index) const
Returns the layout at the corresponding index.
void setAllowEmptyLayout(bool allowEmpty)
Sets whether an optional empty layout ("not set") option is present in the model.
QVariant data(const QModelIndex &index, int role) const override
bool allowEmptyLayout() const
Returns true if the model allows the empty layout ("not set") choice.
QgsLayoutManagerModel(QgsLayoutManager *manager, QObject *parent=nullptr)
Constructor for QgsLayoutManagerModel, showing the layouts from the specified manager.
int rowCount(const QModelIndex &parent) const override
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override
void setFilterString(const QString &filter)
Sets a filter string, such that only layouts with names containing the specified string will be shown...
void setFilters(QgsLayoutManagerProxyModel::Filters filters)
Sets the current filters used for filtering available layouts.
QgsLayoutManagerProxyModel::Filters filters() const
Returns the current filters used for filtering available layouts.
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override
@ FilterReports
Includes reports.
@ FilterPrintLayouts
Includes print layouts.
QgsLayoutManagerProxyModel(QObject *parent=nullptr)
Constructor for QgsLayoutManagerProxyModel.
Manages storage of a set of layouts.
bool readXml(const QDomElement &element, const QDomDocument &doc)
Reads the manager's state from a DOM element, restoring all layouts present in the XML document.
QgsLayoutManager(QgsProject *project=nullptr)
Constructor for QgsLayoutManager.
QList< QgsMasterLayoutInterface * > layouts() const
Returns a list of all layouts contained in the manager.
QList< QgsPrintLayout * > printLayouts() const
Returns a list of all print layouts contained in the manager.
void layoutAboutToBeRemoved(const QString &name)
Emitted when a layout is about to be removed from the manager.
~QgsLayoutManager() override
bool removeLayout(QgsMasterLayoutInterface *layout)
Removes a layout from the manager.
QgsMasterLayoutInterface * layoutByName(const QString &name) const
Returns the layout with a matching name, or nullptr if no matching layouts were found.
void clear()
Removes and deletes all layouts from the manager.
bool addLayout(QgsMasterLayoutInterface *layout)
Adds a layout to the manager.
void layoutAboutToBeAdded(const QString &name)
Emitted when a layout is about to be added to the manager.
bool accept(QgsStyleEntityVisitorInterface *visitor) const
Accepts the specified style entity visitor, causing it to visit all style entities associated within ...
void layoutRenamed(QgsMasterLayoutInterface *layout, const QString &newName)
Emitted when a layout is renamed.
void layoutRemoved(const QString &name)
Emitted when a layout was removed from the manager.
QDomElement writeXml(QDomDocument &doc) const
Returns a DOM element representing the state of the manager.
QString generateUniqueTitle(QgsMasterLayoutInterface::Type type=QgsMasterLayoutInterface::PrintLayout) const
Generates a unique title for a new layout of the specified type, which does not clash with any alread...
void layoutAdded(const QString &name)
Emitted when a layout has been added to the manager.
QgsMasterLayoutInterface * duplicateLayout(const QgsMasterLayoutInterface *layout, const QString &newName)
Duplicates an existing layout from the manager.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:51
Interface for master layout type objects, such as print layouts and reports.
virtual QString name() const =0
Returns the layout's name.
virtual QgsMasterLayoutInterface::Type layoutType() const =0
Returns the master layout type.
@ PrintLayout
Individual print layout (QgsPrintLayout)
virtual QgsMasterLayoutInterface * clone() const =0
Creates a clone of the layout.
virtual void setName(const QString &name)=0
Sets the layout's name.
Print layout, a QgsLayout subclass for static or atlas-based layouts.
void nameChanged(const QString &name)
Emitted when the layout's name is changed.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:101
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:467
QgsPathResolver pathResolver() const
Returns path resolver object with considering whether the project uses absolute or relative paths and...
const QgsLayoutManager * layoutManager() const
Returns the project's layout manager, which manages print layouts, atlases and reports within the pro...
void setDirty(bool b=true)
Flag the project as dirty (modified).
Definition: qgsproject.cpp:518
The class is used as a container of context for various read/write operations on other objects.
void setPathResolver(const QgsPathResolver &resolver)
Sets up path resolver for conversion between relative and absolute paths.
Scoped object for logging of the runtime for a single operation or group of operations.
void switchTask(const QString &name)
Switches the current task managed by the scoped profile to a new task with the given name.
An interface for classes which can visit style entity (e.g.
virtual bool visitExit(const QgsStyleEntityVisitorInterface::Node &node)
Called when the visitor stops visiting a node.
virtual bool visitEnter(const QgsStyleEntityVisitorInterface::Node &node)
Called when the visitor starts visiting a node.
Contains information relating to a node (i.e.