QGIS API Documentation  3.2.0-Bonn (bc43194)
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:421
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
QgsLayoutMeasurement convert(const QgsLayoutMeasurement &measurement, QgsUnitTypes::LayoutUnit targetUnits) const
Converts a measurement from one unit to another.
friend class QgsLayoutModel
Definition: qgslayout.h:739
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
Returns value for the given key. If the key is not stored, default value will be used.
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
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
void deselectAll()
Clears any selected items in the layout.
Definition: qgslayout.cpp:165
void updateZValues(bool addUndoCommands=true)
Resets the z-values of items based on their position in the internal z order list.
Definition: qgslayout.cpp:896
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...
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
Returns 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 * > selectedLayoutItems(bool includeLockedItems=true)
Returns list of selected layout items.
Definition: qgslayout.cpp:138
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 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:85
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.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:49
QStringList keys() const
Returns 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:734
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:737
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:738
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
virtual QString uuid() const
Returns the item identification string.
void addLayoutItem(QgsLayoutItem *item)
Adds an item to the layout.
Definition: qgslayout.cpp:535
QgsLayoutMeasurement convertFromLayoutUnits(double length, QgsUnitTypes::LayoutUnit unit) const
Converts a length measurement from the layout&#39;s native units to a specified target unit...
Definition: qgslayout.cpp:340
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.
QgsLayoutItem * layoutItemAt(QPointF position, bool ignoreLocked=false) const
Returns the topmost layout item at a specified position.
Definition: qgslayout.cpp:289
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:735