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