QGIS API Documentation  3.0.2-Girona (307d082)
qgslayout.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgslayout.cpp
3  -------------------
4  begin : June 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 
17 #include "qgslayout.h"
18 #include "qgslayoutitem.h"
19 #include "qgslayoutmodel.h"
22 #include "qgsreadwritecontext.h"
23 #include "qgsproject.h"
25 #include "qgslayoutitemgroup.h"
27 #include "qgslayoutmultiframe.h"
28 #include "qgslayoutitemmap.h"
29 #include "qgslayoutundostack.h"
31 
33  : mProject( project )
34  , mRenderContext( new QgsLayoutRenderContext( this ) )
35  , mReportContext( new QgsLayoutReportContext( this ) )
36  , mSnapper( QgsLayoutSnapper( this ) )
37  , mGridSettings( this )
38  , mPageCollection( new QgsLayoutPageCollection( this ) )
39  , mUndoStack( new QgsLayoutUndoStack( this ) )
40 {
41  // just to make sure - this should be the default, but maybe it'll change in some future Qt version...
42  setBackgroundBrush( Qt::NoBrush );
43  mItemsModel.reset( new QgsLayoutModel( this ) );
44 }
45 
47 {
48  // no need for undo commands when we're destroying the layout
49  mUndoStack->blockCommands( true );
50 
51  deleteAndRemoveMultiFrames();
52 
53  // make sure that all layout items are removed before
54  // this class is deconstructed - to avoid segfaults
55  // when layout items access in destructor layout that isn't valid anymore
56 
57  // since deletion of some item types (e.g. groups) trigger deletion
58  // of other items, we have to do this careful, one at a time...
59  QList<QGraphicsItem *> itemList = items();
60  bool deleted = true;
61  while ( deleted )
62  {
63  deleted = false;
64  for ( QGraphicsItem *item : qgis::as_const( itemList ) )
65  {
66  if ( dynamic_cast< QgsLayoutItem * >( item ) && !dynamic_cast< QgsLayoutItemPage *>( item ) )
67  {
68  delete item;
69  deleted = true;
70  break;
71  }
72  }
73  itemList = items();
74  }
75 
76  mItemsModel.reset(); // manually delete, so we can control order of destruction
77 }
78 
80 {
81  QDomDocument currentDoc;
82 
83  QgsReadWriteContext context;
84  QDomElement elem = writeXml( currentDoc, context );
85  currentDoc.appendChild( elem );
86 
87  std::unique_ptr< QgsLayout > newLayout = qgis::make_unique< QgsLayout >( mProject );
88  bool ok = false;
89  newLayout->loadFromTemplate( currentDoc, context, true, &ok );
90  if ( !ok )
91  {
92  return nullptr;
93  }
94 
95  return newLayout.release();
96 }
97 
99 {
100  // default to a A4 landscape page
101  QgsLayoutItemPage *page = new QgsLayoutItemPage( this );
103  mPageCollection->addPage( page );
104  mUndoStack->stack()->clear();
105 }
106 
108 {
109  deleteAndRemoveMultiFrames();
110 
111  //delete all non paper items
112  const QList<QGraphicsItem *> itemList = items();
113  for ( QGraphicsItem *item : itemList )
114  {
115  QgsLayoutItem *cItem = dynamic_cast<QgsLayoutItem *>( item );
116  QgsLayoutItemPage *pItem = dynamic_cast<QgsLayoutItemPage *>( item );
117  if ( cItem && !pItem )
118  {
119  removeLayoutItemPrivate( cItem );
120  }
121  }
122  mItemsModel->clear();
123 
124  mPageCollection->clear();
125  mUndoStack->stack()->clear();
126 }
127 
129 {
130  return mProject;
131 }
132 
134 {
135  return mItemsModel.get();
136 }
137 
138 QList<QgsLayoutItem *> QgsLayout::selectedLayoutItems( const bool includeLockedItems )
139 {
140  QList<QgsLayoutItem *> layoutItemList;
141 
142  const QList<QGraphicsItem *> graphicsItemList = selectedItems();
143  for ( QGraphicsItem *item : graphicsItemList )
144  {
145  QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item );
146  if ( layoutItem && ( includeLockedItems || !layoutItem->isLocked() ) )
147  {
148  layoutItemList.push_back( layoutItem );
149  }
150  }
151 
152  return layoutItemList;
153 }
154 
156 {
157  whileBlocking( this )->deselectAll();
158  if ( item )
159  {
160  item->setSelected( true );
161  }
162  emit selectedItemChanged( item );
163 }
164 
166 {
167  //we can't use QGraphicsScene::clearSelection, as that emits no signals
168  //and we don't know which items are being deselected
169  //accordingly, we can't inform the layout model of selection changes
170  //instead, do the clear selection manually...
171  const QList<QGraphicsItem *> selectedItemList = selectedItems();
172  for ( QGraphicsItem *item : selectedItemList )
173  {
174  if ( QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item ) )
175  {
176  layoutItem->setSelected( false );
177  }
178  }
179  emit selectedItemChanged( nullptr );
180 }
181 
182 bool QgsLayout::raiseItem( QgsLayoutItem *item, bool deferUpdate )
183 {
184  //model handles reordering items
185  bool result = mItemsModel->reorderItemUp( item );
186  if ( result && !deferUpdate )
187  {
188  //update all positions
189  updateZValues();
190  update();
191  }
192  return result;
193 }
194 
195 bool QgsLayout::lowerItem( QgsLayoutItem *item, bool deferUpdate )
196 {
197  //model handles reordering items
198  bool result = mItemsModel->reorderItemDown( item );
199  if ( result && !deferUpdate )
200  {
201  //update all positions
202  updateZValues();
203  update();
204  }
205  return result;
206 }
207 
208 bool QgsLayout::moveItemToTop( QgsLayoutItem *item, bool deferUpdate )
209 {
210  //model handles reordering items
211  bool result = mItemsModel->reorderItemToTop( item );
212  if ( result && !deferUpdate )
213  {
214  //update all positions
215  updateZValues();
216  update();
217  }
218  return result;
219 }
220 
221 bool QgsLayout::moveItemToBottom( QgsLayoutItem *item, bool deferUpdate )
222 {
223  //model handles reordering items
224  bool result = mItemsModel->reorderItemToBottom( item );
225  if ( result && !deferUpdate )
226  {
227  //update all positions
228  updateZValues();
229  update();
230  }
231  return result;
232 }
233 
234 QgsLayoutItem *QgsLayout::itemByUuid( const QString &uuid, bool includeTemplateUuids ) const
235 {
236  QList<QgsLayoutItem *> itemList;
237  layoutItems( itemList );
238  for ( QgsLayoutItem *item : qgis::as_const( itemList ) )
239  {
240  if ( item->uuid() == uuid )
241  return item;
242  else if ( includeTemplateUuids && item->mTemplateUuid == uuid )
243  return item;
244  }
245 
246  return nullptr;
247 }
248 
249 QgsLayoutItem *QgsLayout::itemByTemplateUuid( const QString &uuid ) const
250 {
251  QList<QgsLayoutItem *> itemList;
252  layoutItems( itemList );
253  for ( QgsLayoutItem *item : qgis::as_const( itemList ) )
254  {
255  if ( item->mTemplateUuid == uuid )
256  return item;
257  }
258 
259  return nullptr;
260 }
261 
262 QgsLayoutItem *QgsLayout::itemById( const QString &id ) const
263 {
264  const QList<QGraphicsItem *> itemList = items();
265  for ( QGraphicsItem *item : itemList )
266  {
267  QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item );
268  if ( layoutItem && layoutItem->id() == id )
269  {
270  return layoutItem;
271  }
272  }
273  return nullptr;
274 }
275 
276 QgsLayoutMultiFrame *QgsLayout::multiFrameByUuid( const QString &uuid, bool includeTemplateUuids ) const
277 {
278  for ( QgsLayoutMultiFrame *mf : mMultiFrames )
279  {
280  if ( mf->uuid() == uuid )
281  return mf;
282  else if ( includeTemplateUuids && mf->mTemplateUuid == uuid )
283  return mf;
284  }
285 
286  return nullptr;
287 }
288 
289 QgsLayoutItem *QgsLayout::layoutItemAt( QPointF position, const bool ignoreLocked ) const
290 {
291  return layoutItemAt( position, nullptr, ignoreLocked );
292 }
293 
294 QgsLayoutItem *QgsLayout::layoutItemAt( QPointF position, const QgsLayoutItem *belowItem, const bool ignoreLocked ) const
295 {
296  //get a list of items which intersect the specified position, in descending z order
297  const QList<QGraphicsItem *> itemList = items( position, Qt::IntersectsItemShape, Qt::DescendingOrder );
298 
299  bool foundBelowItem = false;
300  for ( QGraphicsItem *graphicsItem : itemList )
301  {
302  QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( graphicsItem );
303  QgsLayoutItemPage *paperItem = dynamic_cast<QgsLayoutItemPage *>( layoutItem );
304  if ( layoutItem && !paperItem )
305  {
306  // If we are not checking for a an item below a specified item, or if we've
307  // already found that item, then we've found our target
308  if ( ( ! belowItem || foundBelowItem ) && ( !ignoreLocked || !layoutItem->isLocked() ) )
309  {
310  return layoutItem;
311  }
312  else
313  {
314  if ( layoutItem == belowItem )
315  {
316  //Target item is next in list
317  foundBelowItem = true;
318  }
319  }
320  }
321  }
322  return nullptr;
323 }
324 
325 double QgsLayout::convertToLayoutUnits( const QgsLayoutMeasurement &measurement ) const
326 {
327  return mRenderContext->measurementConverter().convert( measurement, mUnits ).length();
328 }
329 
331 {
332  return mRenderContext->measurementConverter().convert( size, mUnits ).toQSizeF();
333 }
334 
335 QPointF QgsLayout::convertToLayoutUnits( const QgsLayoutPoint &point ) const
336 {
337  return mRenderContext->measurementConverter().convert( point, mUnits ).toQPointF();
338 }
339 
341 {
342  return mRenderContext->measurementConverter().convert( QgsLayoutMeasurement( length, mUnits ), unit );
343 }
344 
346 {
347  return mRenderContext->measurementConverter().convert( QgsLayoutSize( size.width(), size.height(), mUnits ), unit );
348 }
349 
351 {
352  return mRenderContext->measurementConverter().convert( QgsLayoutPoint( point.x(), point.y(), mUnits ), unit );
353 }
354 
356 {
357  return *mRenderContext;
358 }
359 
361 {
362  return *mRenderContext;
363 }
364 
366 {
367  return *mReportContext;
368 }
369 
371 {
372  return *mReportContext;
373 }
374 
376 {
377  mGridSettings.loadFromSettings();
378  mPageCollection->redraw();
379 }
380 
382 {
383  return mPageCollection->guides();
384 }
385 
387 {
388  return mPageCollection->guides();
389 }
390 
392 {
396  if ( mReportContext->layer() )
397  context.appendScope( QgsExpressionContextUtils::layerScope( mReportContext->layer() ) );
398 
400  return context;
401 }
402 
403 void QgsLayout::setCustomProperty( const QString &key, const QVariant &value )
404 {
405  mCustomProperties.setValue( key, value );
406 
407  if ( key.startsWith( QLatin1String( "variable" ) ) )
408  emit variablesChanged();
409 }
410 
411 QVariant QgsLayout::customProperty( const QString &key, const QVariant &defaultValue ) const
412 {
413  return mCustomProperties.value( key, defaultValue );
414 }
415 
416 void QgsLayout::removeCustomProperty( const QString &key )
417 {
418  mCustomProperties.remove( key );
419 }
420 
421 QStringList QgsLayout::customProperties() const
422 {
423  return mCustomProperties.keys();
424 }
425 
427 {
428  // prefer explicitly set reference map
429  if ( QgsLayoutItemMap *map = qobject_cast< QgsLayoutItemMap * >( itemByUuid( mWorldFileMapId ) ) )
430  return map;
431 
432  // else try to find largest map
433  QList< QgsLayoutItemMap * > maps;
434  layoutItems( maps );
435  QgsLayoutItemMap *largestMap = nullptr;
436  double largestMapArea = 0;
437  for ( QgsLayoutItemMap *map : qgis::as_const( maps ) )
438  {
439  double area = map->rect().width() * map->rect().height();
440  if ( area > largestMapArea )
441  {
442  largestMapArea = area;
443  largestMap = map;
444  }
445  }
446  return largestMap;
447 }
448 
450 {
451  mWorldFileMapId = map ? map->uuid() : QString();
452  mProject->setDirty( true );
453 }
454 
456 {
457  return mPageCollection.get();
458 }
459 
461 {
462  return mPageCollection.get();
463 }
464 
465 QRectF QgsLayout::layoutBounds( bool ignorePages, double margin ) const
466 {
467  //start with an empty rectangle
468  QRectF bounds;
469 
470  //add all layout items and pages which are in the layout
471  Q_FOREACH ( const QGraphicsItem *item, items() )
472  {
473  const QgsLayoutItem *layoutItem = dynamic_cast<const QgsLayoutItem *>( item );
474  if ( !layoutItem )
475  continue;
476 
477  bool isPage = layoutItem->type() == QgsLayoutItemRegistry::LayoutPage;
478  if ( !isPage || !ignorePages )
479  {
480  //expand bounds with current item's bounds
481  QRectF itemBounds;
482  if ( isPage )
483  {
484  // for pages we only consider the item's rect - not the bounding rect
485  // as the bounding rect contains extra padding
486  itemBounds = layoutItem->mapToScene( layoutItem->rect() ).boundingRect();
487  }
488  else
489  itemBounds = item->sceneBoundingRect();
490 
491  if ( bounds.isValid() )
492  bounds = bounds.united( itemBounds );
493  else
494  bounds = itemBounds;
495  }
496  }
497 
498  if ( bounds.isValid() && margin > 0.0 )
499  {
500  //finally, expand bounds out by specified margin of page size
501  double maxWidth = mPageCollection->maximumPageWidth();
502  bounds.adjust( -maxWidth * margin, -maxWidth * margin, maxWidth * margin, maxWidth * margin );
503  }
504 
505  return bounds;
506 
507 }
508 
509 QRectF QgsLayout::pageItemBounds( int page, bool visibleOnly ) const
510 {
511  //start with an empty rectangle
512  QRectF bounds;
513 
514  //add all QgsLayoutItems on page
515  const QList<QGraphicsItem *> itemList = items();
516  for ( QGraphicsItem *item : itemList )
517  {
518  const QgsLayoutItem *layoutItem = dynamic_cast<const QgsLayoutItem *>( item );
519  if ( layoutItem && layoutItem->type() != QgsLayoutItemRegistry::LayoutPage && layoutItem->page() == page )
520  {
521  if ( visibleOnly && !layoutItem->isVisible() )
522  continue;
523 
524  //expand bounds with current item's bounds
525  if ( bounds.isValid() )
526  bounds = bounds.united( item->sceneBoundingRect() );
527  else
528  bounds = item->sceneBoundingRect();
529  }
530  }
531 
532  return bounds;
533 }
534 
536 {
537  addLayoutItemPrivate( item );
538  QString undoText;
539  if ( QgsLayoutItemAbstractMetadata *metadata = QgsApplication::layoutItemRegistry()->itemMetadata( item->type() ) )
540  {
541  undoText = tr( "Create %1" ).arg( metadata->visibleName() );
542  }
543  else
544  {
545  undoText = tr( "Create Item" );
546  }
547  if ( !mUndoStack->isBlocked() )
548  mUndoStack->push( new QgsLayoutItemAddItemCommand( item, undoText ) );
549 }
550 
552 {
553  std::unique_ptr< QgsLayoutItemDeleteUndoCommand > deleteCommand;
554  if ( !mUndoStack->isBlocked() )
555  {
556  mUndoStack->beginMacro( tr( "Delete Items" ) );
557  deleteCommand.reset( new QgsLayoutItemDeleteUndoCommand( item, tr( "Delete Item" ) ) );
558  }
559  removeLayoutItemPrivate( item );
560  if ( deleteCommand )
561  {
562  mUndoStack->push( deleteCommand.release() );
563  mUndoStack->endMacro();
564  }
565 }
566 
568 {
569  if ( !multiFrame )
570  return;
571 
572  if ( !mMultiFrames.contains( multiFrame ) )
573  mMultiFrames << multiFrame;
574 }
575 
577 {
578  mMultiFrames.removeAll( multiFrame );
579 }
580 
581 QList<QgsLayoutMultiFrame *> QgsLayout::multiFrames() const
582 {
583  return mMultiFrames;
584 }
585 
586 bool QgsLayout::saveAsTemplate( const QString &path, const QgsReadWriteContext &context ) const
587 {
588  QFile templateFile( path );
589  if ( !templateFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
590  {
591  return false;
592  }
593 
594  QDomDocument saveDocument;
595  QDomElement elem = writeXml( saveDocument, context );
596  saveDocument.appendChild( elem );
597 
598  if ( templateFile.write( saveDocument.toByteArray() ) == -1 )
599  return false;
600 
601  return true;
602 }
603 
604 QList< QgsLayoutItem * > QgsLayout::loadFromTemplate( const QDomDocument &document, const QgsReadWriteContext &context, bool clearExisting, bool *ok )
605 {
606  if ( ok )
607  *ok = false;
608 
609  QList< QgsLayoutItem * > result;
610 
611  if ( clearExisting )
612  {
613  clear();
614  }
615 
616  QDomDocument doc;
617 
618  // If this is a 2.x composition template, convert it to a layout template
620  {
621  doc = QgsCompositionConverter::convertCompositionTemplate( document, mProject );
622  }
623  else
624  {
625  doc = document;
626  }
627 
628  // remove all uuid attributes since we don't want duplicates UUIDS
629  QDomNodeList itemsNodes = doc.elementsByTagName( QStringLiteral( "LayoutItem" ) );
630  for ( int i = 0; i < itemsNodes.count(); ++i )
631  {
632  QDomNode itemNode = itemsNodes.at( i );
633  if ( itemNode.isElement() )
634  {
635  itemNode.toElement().removeAttribute( QStringLiteral( "uuid" ) );
636  }
637  }
638  QDomNodeList multiFrameNodes = doc.elementsByTagName( QStringLiteral( "LayoutMultiFrame" ) );
639  for ( int i = 0; i < multiFrameNodes.count(); ++i )
640  {
641  QDomNode multiFrameNode = multiFrameNodes.at( i );
642  if ( multiFrameNode.isElement() )
643  {
644  multiFrameNode.toElement().removeAttribute( QStringLiteral( "uuid" ) );
645  QDomNodeList frameNodes = multiFrameNode.toElement().elementsByTagName( QStringLiteral( "childFrame" ) );
646  QDomNode itemNode = frameNodes.at( i );
647  if ( itemNode.isElement() )
648  {
649  itemNode.toElement().removeAttribute( QStringLiteral( "uuid" ) );
650  }
651  }
652  }
653 
654  //read general settings
655  if ( clearExisting )
656  {
657  QDomElement layoutElem = doc.documentElement();
658  if ( layoutElem.isNull() )
659  {
660  return result;
661  }
662 
663  bool loadOk = readXml( layoutElem, doc, context );
664  if ( !loadOk )
665  {
666  return result;
667  }
668  layoutItems( result );
669  }
670  else
671  {
672  result = addItemsFromXml( doc.documentElement(), doc, context );
673  }
674 
675  if ( ok )
676  *ok = true;
677 
678  return result;
679 }
680 
682 {
683  return mUndoStack.get();
684 }
685 
687 {
688  return mUndoStack.get();
689 }
690 
693 {
694  public:
695 
696  QgsLayoutUndoCommand( QgsLayout *layout, const QString &text, int id, QUndoCommand *parent SIP_TRANSFERTHIS = nullptr )
697  : QgsAbstractLayoutUndoCommand( text, id, parent )
698  , mLayout( layout )
699  {}
700 
701  protected:
702 
703  void saveState( QDomDocument &stateDoc ) const override
704  {
705  stateDoc.clear();
706  QDomElement documentElement = stateDoc.createElement( QStringLiteral( "UndoState" ) );
707  mLayout->writeXmlLayoutSettings( documentElement, stateDoc, QgsReadWriteContext() );
708  stateDoc.appendChild( documentElement );
709  }
710 
711  void restoreState( QDomDocument &stateDoc ) override
712  {
713  if ( !mLayout )
714  {
715  return;
716  }
717 
718  mLayout->readXmlLayoutSettings( stateDoc.documentElement(), stateDoc, QgsReadWriteContext() );
719  mLayout->project()->setDirty( true );
720  }
721 
722  private:
723 
724  QgsLayout *mLayout = nullptr;
725 };
727 
728 QgsAbstractLayoutUndoCommand *QgsLayout::createCommand( const QString &text, int id, QUndoCommand *parent )
729 {
730  return new QgsLayoutUndoCommand( this, text, id, parent );
731 }
732 
733 QgsLayoutItemGroup *QgsLayout::groupItems( const QList<QgsLayoutItem *> &items )
734 {
735  if ( items.size() < 2 )
736  {
737  //not enough items for a group
738  return nullptr;
739  }
740 
741  mUndoStack->beginMacro( tr( "Group Items" ) );
742  std::unique_ptr< QgsLayoutItemGroup > itemGroup( new QgsLayoutItemGroup( this ) );
743  for ( QgsLayoutItem *item : items )
744  {
745  itemGroup->addItem( item );
746  }
747  QgsLayoutItemGroup *returnGroup = itemGroup.get();
748  addLayoutItem( itemGroup.release() );
749 
750  std::unique_ptr< QgsLayoutItemGroupUndoCommand > c( new QgsLayoutItemGroupUndoCommand( QgsLayoutItemGroupUndoCommand::Grouped, returnGroup, this, tr( "Group Items" ) ) );
751  mUndoStack->push( c.release() );
752  mProject->setDirty( true );
753 
754  mUndoStack->endMacro();
755 
756  return returnGroup;
757 }
758 
759 QList<QgsLayoutItem *> QgsLayout::ungroupItems( QgsLayoutItemGroup *group )
760 {
761  QList<QgsLayoutItem *> ungroupedItems;
762  if ( !group )
763  {
764  return ungroupedItems;
765  }
766 
767  mUndoStack->beginMacro( tr( "Ungroup Items" ) );
768  // Call this before removing group items so it can keep note
769  // of contents
770  std::unique_ptr< QgsLayoutItemGroupUndoCommand > c( new QgsLayoutItemGroupUndoCommand( QgsLayoutItemGroupUndoCommand::Ungrouped, group, this, tr( "Ungroup Items" ) ) );
771  mUndoStack->push( c.release() );
772 
773  mProject->setDirty( true );
774 
775  ungroupedItems = group->items();
776  group->removeItems();
777 
778  removeLayoutItem( group );
779  mUndoStack->endMacro();
780 
781  return ungroupedItems;
782 }
783 
785 {
786  mUndoStack->blockCommands( true );
787  mPageCollection->beginPageSizeChange();
788  emit refreshed();
789  mPageCollection->reflow();
790  mPageCollection->endPageSizeChange();
791  mUndoStack->blockCommands( false );
792  update();
793 }
794 
795 void QgsLayout::writeXmlLayoutSettings( QDomElement &element, QDomDocument &document, const QgsReadWriteContext & ) const
796 {
797  mCustomProperties.writeXml( element, document );
798  element.setAttribute( QStringLiteral( "units" ), QgsUnitTypes::encodeUnit( mUnits ) );
799  element.setAttribute( QStringLiteral( "worldFileMap" ), mWorldFileMapId );
800  element.setAttribute( QStringLiteral( "printResolution" ), mRenderContext->dpi() );
801 }
802 
803 QDomElement QgsLayout::writeXml( QDomDocument &document, const QgsReadWriteContext &context ) const
804 {
805  QDomElement element = document.createElement( QStringLiteral( "Layout" ) );
806  auto save = [&]( const QgsLayoutSerializableObject * object )->bool
807  {
808  return object->writeXml( element, document, context );
809  };
810  save( &mSnapper );
811  save( &mGridSettings );
812  save( mPageCollection.get() );
813 
814  //save items except paper items and frame items (they are saved with the corresponding multiframe)
815  const QList<QGraphicsItem *> itemList = items();
816  for ( const QGraphicsItem *graphicsItem : itemList )
817  {
818  if ( const QgsLayoutItem *item = dynamic_cast< const QgsLayoutItem *>( graphicsItem ) )
819  {
820  if ( item->type() == QgsLayoutItemRegistry::LayoutPage )
821  continue;
822 
823  item->writeXml( element, document, context );
824  }
825  }
826 
827  //save multiframes
828  for ( QgsLayoutMultiFrame *mf : mMultiFrames )
829  {
830  mf->writeXml( element, document, context );
831  }
832 
833  writeXmlLayoutSettings( element, document, context );
834  return element;
835 }
836 
837 bool QgsLayout::readXmlLayoutSettings( const QDomElement &layoutElement, const QDomDocument &, const QgsReadWriteContext & )
838 {
839  mCustomProperties.readXml( layoutElement );
840  setUnits( QgsUnitTypes::decodeLayoutUnit( layoutElement.attribute( QStringLiteral( "units" ) ) ) );
841  mWorldFileMapId = layoutElement.attribute( QStringLiteral( "worldFileMap" ) );
842  mRenderContext->setDpi( layoutElement.attribute( QStringLiteral( "printResolution" ), "300" ).toDouble() );
843  emit changed();
844 
845  return true;
846 }
847 
848 void QgsLayout::addLayoutItemPrivate( QgsLayoutItem *item )
849 {
850  addItem( item );
851  updateBounds();
852  mItemsModel->rebuildZList();
853 }
854 
855 void QgsLayout::removeLayoutItemPrivate( QgsLayoutItem *item )
856 {
857  mItemsModel->setItemRemoved( item );
858  // small chance that item is still in a scene - the model may have
859  // rejected the removal for some reason. This is probably not necessary,
860  // but can't hurt...
861  if ( item->scene() )
862  removeItem( item );
863 #if 0 //TODO
864  emit itemRemoved( item );
865 #endif
866  item->cleanup();
867  item->deleteLater();
868 }
869 
870 void QgsLayout::deleteAndRemoveMultiFrames()
871 {
872  qDeleteAll( mMultiFrames );
873  mMultiFrames.clear();
874 }
875 
876 QPointF QgsLayout::minPointFromXml( const QDomElement &elem ) const
877 {
878  double minX = std::numeric_limits<double>::max();
879  double minY = std::numeric_limits<double>::max();
880  const QDomNodeList itemList = elem.elementsByTagName( QStringLiteral( "LayoutItem" ) );
881  bool found = false;
882  for ( int i = 0; i < itemList.size(); ++i )
883  {
884  const QDomElement currentItemElem = itemList.at( i ).toElement();
885 
886  QgsLayoutPoint pos = QgsLayoutPoint::decodePoint( currentItemElem.attribute( QStringLiteral( "position" ) ) );
887  QPointF layoutPoint = convertToLayoutUnits( pos );
888 
889  minX = std::min( minX, layoutPoint.x() );
890  minY = std::min( minY, layoutPoint.y() );
891  found = true;
892  }
893  return found ? QPointF( minX, minY ) : QPointF( 0, 0 );
894 }
895 
896 void QgsLayout::updateZValues( const bool addUndoCommands )
897 {
898  int counter = mItemsModel->zOrderListSize();
899  const QList<QgsLayoutItem *> zOrderList = mItemsModel->zOrderList();
900 
901  if ( addUndoCommands )
902  {
903  mUndoStack->beginMacro( tr( "Change Item Stacking" ) );
904  }
905  for ( QgsLayoutItem *currentItem : zOrderList )
906  {
907  if ( currentItem )
908  {
909  if ( addUndoCommands )
910  {
911  mUndoStack->beginCommand( currentItem, QString() );
912  }
913  currentItem->setZValue( counter );
914  if ( addUndoCommands )
915  {
916  mUndoStack->endCommand();
917  }
918  }
919  --counter;
920  }
921  if ( addUndoCommands )
922  {
923  mUndoStack->endMacro();
924  }
925 }
926 
927 bool QgsLayout::readXml( const QDomElement &layoutElement, const QDomDocument &document, const QgsReadWriteContext &context )
928 {
929  if ( layoutElement.nodeName() != QStringLiteral( "Layout" ) )
930  {
931  return false;
932  }
933 
934  auto restore = [&]( QgsLayoutSerializableObject * object )->bool
935  {
936  return object->readXml( layoutElement, document, context );
937  };
938 
939  blockSignals( true ); // defer changed signal to end
940  readXmlLayoutSettings( layoutElement, document, context );
941  blockSignals( false );
942 
943  restore( mPageCollection.get() );
944  restore( &mSnapper );
945  restore( &mGridSettings );
946  addItemsFromXml( layoutElement, document, context );
947 
948  emit changed();
949 
950  return true;
951 }
952 
953 QList< QgsLayoutItem * > QgsLayout::addItemsFromXml( const QDomElement &parentElement, const QDomDocument &document, const QgsReadWriteContext &context, QPointF *position, bool pasteInPlace )
954 {
955  QList< QgsLayoutItem * > newItems;
956  QList< QgsLayoutMultiFrame * > newMultiFrames;
957 
958  //if we are adding items to a layout which already contains items, we need to make sure
959  //these items are placed at the top of the layout and that zValues are not duplicated
960  //so, calculate an offset which needs to be added to the zValue of created items
961  int zOrderOffset = mItemsModel->zOrderListSize();
962 
963  QPointF pasteShiftPos;
964  int pageNumber = -1;
965  if ( position )
966  {
967  //If we are placing items relative to a certain point, then calculate how much we need
968  //to shift the items by so that they are placed at this point
969  //First, calculate the minimum position from the xml
970  QPointF minItemPos = minPointFromXml( parentElement );
971  //next, calculate how much each item needs to be shifted from its original position
972  //so that it's placed at the correct relative position
973  pasteShiftPos = *position - minItemPos;
974  if ( pasteInPlace )
975  {
976  pageNumber = mPageCollection->pageNumberForPoint( *position );
977  }
978  }
979  // multiframes
980 
981  //TODO - fix this. pasting multiframe frame items has no effect
982  const QDomNodeList multiFrameList = parentElement.elementsByTagName( QStringLiteral( "LayoutMultiFrame" ) );
983  for ( int i = 0; i < multiFrameList.size(); ++i )
984  {
985  const QDomElement multiFrameElem = multiFrameList.at( i ).toElement();
986  const int itemType = multiFrameElem.attribute( QStringLiteral( "type" ) ).toInt();
987  std::unique_ptr< QgsLayoutMultiFrame > mf( QgsApplication::layoutItemRegistry()->createMultiFrame( itemType, this ) );
988  if ( !mf )
989  {
990  // e.g. plugin based item which is no longer available
991  continue;
992  }
993  mf->readXml( multiFrameElem, document, context );
994 
995 #if 0 //TODO?
996  mf->setCreateUndoCommands( true );
997 #endif
998 
999  QgsLayoutMultiFrame *m = mf.get();
1000  this->addMultiFrame( mf.release() );
1001 
1002  //offset z values for frames
1003  //TODO - fix this after fixing multiframe item paste
1004  /*for ( int frameIdx = 0; frameIdx < mf->frameCount(); ++frameIdx )
1005  {
1006  QgsLayoutItemFrame * frame = mf->frame( frameIdx );
1007  frame->setZValue( frame->zValue() + zOrderOffset );
1008 
1009  // also need to shift frames according to position/pasteInPlacePt
1010  }*/
1011  newMultiFrames << m;
1012  }
1013 
1014  const QDomNodeList layoutItemList = parentElement.childNodes();
1015  for ( int i = 0; i < layoutItemList.size(); ++i )
1016  {
1017  const QDomElement currentItemElem = layoutItemList.at( i ).toElement();
1018  if ( currentItemElem.nodeName() != QStringLiteral( "LayoutItem" ) )
1019  continue;
1020 
1021  const int itemType = currentItemElem.attribute( QStringLiteral( "type" ) ).toInt();
1022  std::unique_ptr< QgsLayoutItem > item( QgsApplication::layoutItemRegistry()->createItem( itemType, this ) );
1023  if ( !item )
1024  {
1025  // e.g. plugin based item which is no longer available
1026  continue;
1027  }
1028 
1029  item->readXml( currentItemElem, document, context );
1030  if ( position )
1031  {
1032  if ( pasteInPlace )
1033  {
1034  QgsLayoutPoint posOnPage = QgsLayoutPoint::decodePoint( currentItemElem.attribute( QStringLiteral( "positionOnPage" ) ) );
1035  item->attemptMove( posOnPage, true, false, pageNumber );
1036  }
1037  else
1038  {
1039  item->attemptMoveBy( pasteShiftPos.x(), pasteShiftPos.y() );
1040  }
1041  }
1042 
1043  QgsLayoutItem *layoutItem = item.get();
1044  addLayoutItem( item.release() );
1045  layoutItem->setZValue( layoutItem->zValue() + zOrderOffset );
1046  newItems << layoutItem;
1047  }
1048 
1049  // we now allow items to "post-process", e.g. if they need to setup connections
1050  // to other items in the layout, which may not have existed at the time the
1051  // item's state was restored. E.g. a scalebar may have been restored before the map
1052  // it is linked to
1053  for ( QgsLayoutItem *item : qgis::as_const( newItems ) )
1054  {
1055  item->finalizeRestoreFromXml();
1056  }
1057  for ( QgsLayoutMultiFrame *mf : qgis::as_const( newMultiFrames ) )
1058  {
1059  mf->finalizeRestoreFromXml();
1060  }
1061 
1062  for ( QgsLayoutItem *item : qgis::as_const( newItems ) )
1063  {
1064  item->mTemplateUuid.clear();
1065  }
1066  for ( QgsLayoutMultiFrame *mf : qgis::as_const( newMultiFrames ) )
1067  {
1068  mf->mTemplateUuid.clear();
1069  }
1070 
1071  //Since this function adds items in an order which isn't the z-order, and each item is added to end of
1072  //z order list in turn, it will now be inconsistent with the actual order of items in the scene.
1073  //Make sure z order list matches the actual order of items in the scene.
1074  mItemsModel->rebuildZList();
1075 
1076  return newItems;
1077 }
1078 
1080 {
1081  setSceneRect( layoutBounds( false, 0.05 ) );
1082 }
void setDirty(bool b=true)
Flag the project as dirty (modified).
Definition: qgsproject.cpp:414
The class is used as a container of context for various read/write operations on other objects...
void removeItems()
Removes all items from the group (but does not delete them).
void setSelectedItem(QgsLayoutItem *item)
Clears any selected items and sets item as the current selection.
Definition: qgslayout.cpp:155
bool raiseItem(QgsLayoutItem *item, bool deferUpdate=false)
Raises an item up the z-order.
Definition: qgslayout.cpp:182
QgsLayoutGuideCollection & guides()
Returns a reference to the layout&#39;s guide collection, which manages page snap guides.
Definition: qgslayout.cpp:381
QgsLayoutItem * itemById(const QString &id) const
Returns a layout item given its id.
Definition: qgslayout.cpp:262
QRectF layoutBounds(bool ignorePages=false, double margin=0.0) const
Calculates the bounds of all non-gui items in the layout.
Definition: qgslayout.cpp:465
QgsLayout * clone() const
Creates a clone of the layout.
Definition: qgslayout.cpp:79
Base class for graphical items within a QgsLayout.
#define SIP_TRANSFERTHIS
Definition: qgis_sip.h:46
int type() const override
Returns a unique graphics item type identifier.
QgsLayoutMultiFrame * multiFrameByUuid(const QString &uuid, bool includeTemplateUuids=false) const
Returns the layout multiframe with matching uuid unique identifier, or a nullptr if a matching multif...
Definition: qgslayout.cpp:276
Base class for commands to undo/redo layout and layout object changes.
QgsLayoutUndoStack * undoStack()
Returns a pointer to the layout&#39;s undo stack, which manages undo/redo states for the layout and it&#39;s ...
Definition: qgslayout.cpp:681
friend class QgsLayoutModel
Definition: qgslayout.h:741
QVariant customProperty(const QString &key, const QVariant &defaultValue=QVariant()) const
Read a custom property from the layout.
Definition: qgslayout.cpp:411
Stores information relating to the current reporting context for a layout.
QgsLayoutItemGroup * groupItems(const QList< QgsLayoutItem *> &items)
Creates a new group from a list of layout items and adds the group to the layout. ...
Definition: qgslayout.cpp:733
virtual bool readXml(const QDomElement &layoutElement, const QDomDocument &document, const QgsReadWriteContext &context)
Sets the collection&#39;s state from a DOM element.
Definition: qgslayout.cpp:927
An undo stack for QgsLayouts.
QgsLayoutItem * itemByUuid(const QString &uuid, bool includeTemplateUuids=false) const
Returns the layout item with matching uuid unique identifier, or a nullptr if a matching item could n...
Definition: qgslayout.cpp:234
void loadFromSettings()
Loads grid settings from the application layout settings.
A container for grouping several QgsLayoutItems.
static QDomDocument convertCompositionTemplate(const QDomDocument &document, QgsProject *project)
Convert a composition template document to a layout template.
virtual void setSelected(bool selected)
Sets whether the item should be selected.
QVariant value(const QString &key, const QVariant &defaultValue=QVariant()) const
Return value for the given key. If the key is not stored, default value will be used.
QgsLayoutMeasurement convert(const QgsLayoutMeasurement &measurement, const QgsUnitTypes::LayoutUnit targetUnits) const
Converts a measurement from one unit to another.
QList< QgsLayoutItem *> addItemsFromXml(const QDomElement &parentElement, const QDomDocument &document, const QgsReadWriteContext &context, QPointF *position=nullptr, bool pasteInPlace=false)
Add items from an XML representation to the layout.
Definition: qgslayout.cpp:953
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
void setUnits(QgsUnitTypes::LayoutUnit units)
Sets the native measurement units for the layout.
Definition: qgslayout.h:321
Stores metadata about one layout item class.
QList< QgsLayoutItem * > ungroupItems(QgsLayoutItemGroup *group)
Ungroups items by removing them from an item group and removing the group from the layout...
Definition: qgslayout.cpp:759
void updateBounds()
Updates the scene bounds of the layout.
Definition: qgslayout.cpp:1079
QgsLayoutRenderContext & renderContext()
Returns a reference to the layout&#39;s render context, which stores information relating to the current ...
Definition: qgslayout.cpp:355
void remove(const QString &key)
Remove a key (entry) from the store.
double convertToLayoutUnits(const QgsLayoutMeasurement &measurement) const
Converts a measurement into the layout&#39;s native units.
Definition: qgslayout.cpp:325
QList< QgsLayoutMultiFrame *> multiFrames() const
Returns a list of multi frames contained in the layout.
Definition: qgslayout.cpp:581
Abstract base class for layout items with the ability to distribute the content to several frames (Qg...
This class provides a method of storing points, consisting of an x and y coordinate, for use in QGIS layouts.
void layoutItems(QList< T *> &itemList) const
Returns a list of layout items of a specific type.
Definition: qgslayout.h:121
void removeCustomProperty(const QString &key)
Remove a custom property from the layout.
Definition: qgslayout.cpp:416
void deselectAll()
Clears any selected items in the layout.
Definition: qgslayout.cpp:165
QgsVectorLayer * layer() const
Returns the vector layer associated with the layout&#39;s context.
static QgsLayoutItemRegistry * layoutItemRegistry()
Returns the application&#39;s layout item registry, used for layout item types.
Layout graphical items for displaying a map.
void setValue(const QString &key, const QVariant &value)
Add an entry to the store. If the entry with the keys exists already, it will be overwritten.
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context...
QgsLayoutItem * layoutItemAt(QPointF position, const bool ignoreLocked=false) const
Returns the topmost layout item at a specified position.
Definition: qgslayout.cpp:289
void variablesChanged()
Emitted whenever the expression variables stored in the layout have been changed. ...
This class provides a method of storing measurements for use in QGIS layouts using a variety of diffe...
static bool isCompositionTemplate(const QDomDocument &document)
Check if the given document is a composition template.
static Q_INVOKABLE QgsUnitTypes::LayoutUnit decodeLayoutUnit(const QString &string, bool *ok=nullptr)
Decodes a layout unit from a string.
void refresh()
Forces the layout, and all items contained within it, to refresh.
Definition: qgslayout.cpp:784
QStringList customProperties() const
Return list of keys stored in custom properties for the layout.
Definition: qgslayout.cpp:421
QList< QgsLayoutItem * > items() const
Returns a list of items contained by the group.
QgsLayoutPageCollection * pageCollection()
Returns a pointer to the layout&#39;s page collection, which stores and manages page items in the layout...
Definition: qgslayout.cpp:455
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
QList< QgsLayoutItem *> loadFromTemplate(const QDomDocument &document, const QgsReadWriteContext &context, bool clearExisting=true, bool *ok=nullptr)
Load a layout template document.
Definition: qgslayout.cpp:604
void selectedItemChanged(QgsLayoutItem *selected)
Emitted whenever the selected item changes.
virtual void cleanup()
Called just before a batch of items are deleted, allowing them to run cleanup tasks.
void updateZValues(const bool addUndoCommands=true)
Resets the z-values of items based on their position in the internal z order list.
Definition: qgslayout.cpp:896
void removeMultiFrame(QgsLayoutMultiFrame *multiFrame)
Removes a multiFrame from the layout (but does not delete it).
Definition: qgslayout.cpp:576
Stores and manages the snap guides used by a layout.
Reads and writes project states.
Definition: qgsproject.h:82
int page() const
Returns the page the item is currently on, with the first page returning 0.
QString id() const
Returns the item&#39;s ID name.
QList< QgsLayoutItem * > selectedLayoutItems(const bool includeLockedItems=true)
Returns list of selected layout items.
Definition: qgslayout.cpp:138
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:49
QStringList keys() const
Return list of stored keys.
A manager for a collection of pages in a layout.
QRectF pageItemBounds(int page, bool visibleOnly=false) const
Returns the bounding box of the items contained on a specified page.
Definition: qgslayout.cpp:509
bool lowerItem(QgsLayoutItem *item, bool deferUpdate=false)
Lowers an item down the z-order.
Definition: qgslayout.cpp:195
static Q_INVOKABLE QString encodeUnit(QgsUnitTypes::DistanceUnit unit)
Encodes a distance unit to a string.
QgsLayoutModel * itemsModel()
Returns the items model attached to the layout.
Definition: qgslayout.cpp:133
friend class QgsLayoutItemAddItemCommand
Definition: qgslayout.h:736
virtual void finalizeRestoreFromXml()
Called after all pending items have been restored from XML.
QgsLayoutReportContext & reportContext()
Returns a reference to the layout&#39;s report context, which stores information relating to the current ...
Definition: qgslayout.cpp:365
friend class QgsLayoutUndoCommand
Definition: qgslayout.h:739
QgsExpressionContext createExpressionContext() const override
Creates an expression context relating to the layout&#39;s current state.
Definition: qgslayout.cpp:391
bool moveItemToBottom(QgsLayoutItem *item, bool deferUpdate=false)
Lowers an item down to the bottom of the z-order.
Definition: qgslayout.cpp:221
void clear()
Clears the layout.
Definition: qgslayout.cpp:107
QgsLayoutItemMap * referenceMap() const
Returns the map item which will be used to generate corresponding world files when the layout is expo...
Definition: qgslayout.cpp:426
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:224
double length() const
Returns the length of the measurement.
bool saveAsTemplate(const QString &path, const QgsReadWriteContext &context) const
Saves the layout as a template at the given file path.
Definition: qgslayout.cpp:586
const QgsLayoutMeasurementConverter & measurementConverter() const
Returns the layout measurement converter to be used in the layout.
void removeLayoutItem(QgsLayoutItem *item)
Removes an item from the layout.
Definition: qgslayout.cpp:551
static QgsExpressionContextScope * layoutScope(const QgsLayout *layout)
Creates a new scope which contains variables and functions relating to a QgsLayout layout...
void refreshed()
Is emitted when the layout has been refreshed and items should also be refreshed and updated...
friend class QgsLayoutItemGroupUndoCommand
Definition: qgslayout.h:740
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
virtual QString uuid() const
Returns the item identification string.
QgsLayoutMeasurement convertFromLayoutUnits(const double length, const QgsUnitTypes::LayoutUnit unit) const
Converts a length measurement from the layout&#39;s native units to a specified target unit...
Definition: qgslayout.cpp:340
void addLayoutItem(QgsLayoutItem *item)
Adds an item to the layout.
Definition: qgslayout.cpp:535
Stores information relating to the current rendering settings for a layout.
void setReferenceMap(QgsLayoutItemMap *map)
Sets the map item which will be used to generate corresponding world files when the layout is exporte...
Definition: qgslayout.cpp:449
An interface for layout objects which can be stored and read from DOM elements.
~QgsLayout() override
Definition: qgslayout.cpp:46
void initializeDefaults()
Initializes an empty layout, e.g.
Definition: qgslayout.cpp:98
QgsProject * project() const
The project associated with the layout.
Definition: qgslayout.cpp:128
void setCustomProperty(const QString &key, const QVariant &value)
Set a custom property for the layout.
Definition: qgslayout.cpp:403
LayoutUnit
Layout measurement units.
Definition: qgsunittypes.h:114
virtual QDomElement writeXml(QDomDocument &document, const QgsReadWriteContext &context) const
Returns the layout&#39;s state encapsulated in a DOM element.
Definition: qgslayout.cpp:803
bool isLocked() const
Returns true if the item is locked, and cannot be interacted with using the mouse.
QgsAbstractLayoutUndoCommand * createCommand(const QString &text, int id=0, QUndoCommand *parent=nullptr) override
Creates a new layout undo command with the specified text and parent.
Definition: qgslayout.cpp:728
void setPageSize(const QgsLayoutSize &size)
Sets the size of the page.
A model for items attached to a layout.
Manages snapping grids and preset snap lines in a layout, and handles snapping points to the nearest ...
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
This class provides a method of storing sizes, consisting of a width and height, for use in QGIS layo...
Definition: qgslayoutsize.h:40
void changed()
Is emitted when properties of the layout change.
bool moveItemToTop(QgsLayoutItem *item, bool deferUpdate=false)
Raises an item up to the top of the z-order.
Definition: qgslayout.cpp:208
QgsLayoutItem * itemByTemplateUuid(const QString &uuid) const
Returns the layout item with matching template uuid unique identifier, or a nullptr if a matching ite...
Definition: qgslayout.cpp:249
void reloadSettings()
Refreshes the layout when global layout related options change.
Definition: qgslayout.cpp:375
void addMultiFrame(QgsLayoutMultiFrame *multiFrame)
Adds a multiFrame to the layout.
Definition: qgslayout.cpp:567
QgsLayout(QgsProject *project)
Construct a new layout linked to the specified project.
Definition: qgslayout.cpp:32
static QgsLayoutPoint decodePoint(const QString &string)
Decodes a point from a string.
Item representing the paper in a layout.
friend class QgsLayoutItemDeleteUndoCommand
Definition: qgslayout.h:737