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