QGIS API Documentation  3.6.0-Noosa (5873452)
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  Q_FOREACH ( const QGraphicsItem *item, items() )
474  {
475  const QgsLayoutItem *layoutItem = dynamic_cast<const QgsLayoutItem *>( item );
476  if ( !layoutItem )
477  continue;
478 
479  bool isPage = layoutItem->type() == QgsLayoutItemRegistry::LayoutPage;
480  if ( !isPage || !ignorePages )
481  {
482  //expand bounds with current item's bounds
483  QRectF itemBounds;
484  if ( isPage )
485  {
486  // for pages we only consider the item's rect - not the bounding rect
487  // as the bounding rect contains extra padding
488  itemBounds = layoutItem->mapToScene( layoutItem->rect() ).boundingRect();
489  }
490  else
491  itemBounds = item->sceneBoundingRect();
492 
493  if ( bounds.isValid() )
494  bounds = bounds.united( itemBounds );
495  else
496  bounds = itemBounds;
497  }
498  }
499 
500  if ( bounds.isValid() && margin > 0.0 )
501  {
502  //finally, expand bounds out by specified margin of page size
503  double maxWidth = mPageCollection->maximumPageWidth();
504  bounds.adjust( -maxWidth * margin, -maxWidth * margin, maxWidth * margin, maxWidth * margin );
505  }
506 
507  return bounds;
508 
509 }
510 
511 QRectF QgsLayout::pageItemBounds( int page, bool visibleOnly ) const
512 {
513  //start with an empty rectangle
514  QRectF bounds;
515 
516  //add all QgsLayoutItems on page
517  const QList<QGraphicsItem *> itemList = items();
518  for ( QGraphicsItem *item : itemList )
519  {
520  const QgsLayoutItem *layoutItem = dynamic_cast<const QgsLayoutItem *>( item );
521  if ( layoutItem && layoutItem->type() != QgsLayoutItemRegistry::LayoutPage && layoutItem->page() == page )
522  {
523  if ( visibleOnly && !layoutItem->isVisible() )
524  continue;
525 
526  //expand bounds with current item's bounds
527  if ( bounds.isValid() )
528  bounds = bounds.united( item->sceneBoundingRect() );
529  else
530  bounds = item->sceneBoundingRect();
531  }
532  }
533 
534  return bounds;
535 }
536 
538 {
539  addLayoutItemPrivate( item );
540  QString undoText;
541  if ( QgsLayoutItemAbstractMetadata *metadata = QgsApplication::layoutItemRegistry()->itemMetadata( item->type() ) )
542  {
543  undoText = tr( "Create %1" ).arg( metadata->visibleName() );
544  }
545  else
546  {
547  undoText = tr( "Create Item" );
548  }
549  if ( !mUndoStack->isBlocked() )
550  mUndoStack->push( new QgsLayoutItemAddItemCommand( item, undoText ) );
551 }
552 
554 {
555  std::unique_ptr< QgsLayoutItemDeleteUndoCommand > deleteCommand;
556  if ( !mUndoStack->isBlocked() )
557  {
558  mUndoStack->beginMacro( tr( "Delete Items" ) );
559  deleteCommand.reset( new QgsLayoutItemDeleteUndoCommand( item, tr( "Delete Item" ) ) );
560  }
561  removeLayoutItemPrivate( item );
562  if ( deleteCommand )
563  {
564  mUndoStack->push( deleteCommand.release() );
565  mUndoStack->endMacro();
566  }
567 }
568 
570 {
571  if ( !multiFrame )
572  return;
573 
574  if ( !mMultiFrames.contains( multiFrame ) )
575  mMultiFrames << multiFrame;
576 }
577 
579 {
580  mMultiFrames.removeAll( multiFrame );
581 }
582 
583 QList<QgsLayoutMultiFrame *> QgsLayout::multiFrames() const
584 {
585  return mMultiFrames;
586 }
587 
588 bool QgsLayout::saveAsTemplate( const QString &path, const QgsReadWriteContext &context ) const
589 {
590  QFile templateFile( path );
591  if ( !templateFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
592  {
593  return false;
594  }
595 
596  QDomDocument saveDocument;
597  QDomElement elem = writeXml( saveDocument, context );
598  saveDocument.appendChild( elem );
599 
600  if ( templateFile.write( saveDocument.toByteArray() ) == -1 )
601  return false;
602 
603  return true;
604 }
605 
606 QList< QgsLayoutItem * > QgsLayout::loadFromTemplate( const QDomDocument &document, const QgsReadWriteContext &context, bool clearExisting, bool *ok )
607 {
608  if ( ok )
609  *ok = false;
610 
611  QList< QgsLayoutItem * > result;
612 
613  if ( clearExisting )
614  {
615  clear();
616  }
617 
618  QDomDocument doc;
619 
620  // If this is a 2.x composition template, convert it to a layout template
622  {
623  doc = QgsCompositionConverter::convertCompositionTemplate( document, mProject );
624  }
625  else
626  {
627  doc = document;
628  }
629 
630  // remove all uuid attributes since we don't want duplicates UUIDS
631  QDomNodeList itemsNodes = doc.elementsByTagName( QStringLiteral( "LayoutItem" ) );
632  for ( int i = 0; i < itemsNodes.count(); ++i )
633  {
634  QDomNode itemNode = itemsNodes.at( i );
635  if ( itemNode.isElement() )
636  {
637  itemNode.toElement().removeAttribute( QStringLiteral( "uuid" ) );
638  }
639  }
640  QDomNodeList multiFrameNodes = doc.elementsByTagName( QStringLiteral( "LayoutMultiFrame" ) );
641  for ( int i = 0; i < multiFrameNodes.count(); ++i )
642  {
643  QDomNode multiFrameNode = multiFrameNodes.at( i );
644  if ( multiFrameNode.isElement() )
645  {
646  multiFrameNode.toElement().removeAttribute( QStringLiteral( "uuid" ) );
647  QDomNodeList frameNodes = multiFrameNode.toElement().elementsByTagName( QStringLiteral( "childFrame" ) );
648  QDomNode itemNode = frameNodes.at( i );
649  if ( itemNode.isElement() )
650  {
651  itemNode.toElement().removeAttribute( QStringLiteral( "uuid" ) );
652  }
653  }
654  }
655 
656  //read general settings
657  if ( clearExisting )
658  {
659  QDomElement layoutElem = doc.documentElement();
660  if ( layoutElem.isNull() )
661  {
662  return result;
663  }
664 
665  bool loadOk = readXml( layoutElem, doc, context );
666  if ( !loadOk )
667  {
668  return result;
669  }
670  layoutItems( result );
671  }
672  else
673  {
674  result = addItemsFromXml( doc.documentElement(), doc, context );
675  }
676 
677  if ( ok )
678  *ok = true;
679 
680  return result;
681 }
682 
684 {
685  return mUndoStack.get();
686 }
687 
689 {
690  return mUndoStack.get();
691 }
692 
695 {
696  public:
697 
698  QgsLayoutUndoCommand( QgsLayout *layout, const QString &text, int id, QUndoCommand *parent SIP_TRANSFERTHIS = nullptr )
699  : QgsAbstractLayoutUndoCommand( text, id, parent )
700  , mLayout( layout )
701  {}
702 
703  protected:
704 
705  void saveState( QDomDocument &stateDoc ) const override
706  {
707  stateDoc.clear();
708  QDomElement documentElement = stateDoc.createElement( QStringLiteral( "UndoState" ) );
709  mLayout->writeXmlLayoutSettings( documentElement, stateDoc, QgsReadWriteContext() );
710  stateDoc.appendChild( documentElement );
711  }
712 
713  void restoreState( QDomDocument &stateDoc ) override
714  {
715  if ( !mLayout )
716  {
717  return;
718  }
719 
720  mLayout->readXmlLayoutSettings( stateDoc.documentElement(), stateDoc, QgsReadWriteContext() );
721  mLayout->project()->setDirty( true );
722  }
723 
724  private:
725 
726  QgsLayout *mLayout = nullptr;
727 };
729 
730 QgsAbstractLayoutUndoCommand *QgsLayout::createCommand( const QString &text, int id, QUndoCommand *parent )
731 {
732  return new QgsLayoutUndoCommand( this, text, id, parent );
733 }
734 
735 QgsLayoutItemGroup *QgsLayout::groupItems( const QList<QgsLayoutItem *> &items )
736 {
737  if ( items.size() < 2 )
738  {
739  //not enough items for a group
740  return nullptr;
741  }
742 
743  mUndoStack->beginMacro( tr( "Group Items" ) );
744  std::unique_ptr< QgsLayoutItemGroup > itemGroup( new QgsLayoutItemGroup( this ) );
745  for ( QgsLayoutItem *item : items )
746  {
747  itemGroup->addItem( item );
748  }
749  QgsLayoutItemGroup *returnGroup = itemGroup.get();
750  addLayoutItem( itemGroup.release() );
751 
752  std::unique_ptr< QgsLayoutItemGroupUndoCommand > c( new QgsLayoutItemGroupUndoCommand( QgsLayoutItemGroupUndoCommand::Grouped, returnGroup, this, tr( "Group Items" ) ) );
753  mUndoStack->push( c.release() );
754  mProject->setDirty( true );
755 
756  mUndoStack->endMacro();
757 
758  return returnGroup;
759 }
760 
761 QList<QgsLayoutItem *> QgsLayout::ungroupItems( QgsLayoutItemGroup *group )
762 {
763  QList<QgsLayoutItem *> ungroupedItems;
764  if ( !group )
765  {
766  return ungroupedItems;
767  }
768 
769  mUndoStack->beginMacro( tr( "Ungroup Items" ) );
770  // Call this before removing group items so it can keep note
771  // of contents
772  std::unique_ptr< QgsLayoutItemGroupUndoCommand > c( new QgsLayoutItemGroupUndoCommand( QgsLayoutItemGroupUndoCommand::Ungrouped, group, this, tr( "Ungroup Items" ) ) );
773  mUndoStack->push( c.release() );
774 
775  mProject->setDirty( true );
776 
777  ungroupedItems = group->items();
778  group->removeItems();
779 
780  removeLayoutItem( group );
781  mUndoStack->endMacro();
782 
783  return ungroupedItems;
784 }
785 
787 {
788  mUndoStack->blockCommands( true );
789  mPageCollection->beginPageSizeChange();
790  emit refreshed();
791  mPageCollection->reflow();
792  mPageCollection->endPageSizeChange();
793  mUndoStack->blockCommands( false );
794  update();
795 }
796 
797 void QgsLayout::writeXmlLayoutSettings( QDomElement &element, QDomDocument &document, const QgsReadWriteContext & ) const
798 {
799  mCustomProperties.writeXml( element, document );
800  element.setAttribute( QStringLiteral( "units" ), QgsUnitTypes::encodeUnit( mUnits ) );
801  element.setAttribute( QStringLiteral( "worldFileMap" ), mWorldFileMapId );
802  element.setAttribute( QStringLiteral( "printResolution" ), mRenderContext->dpi() );
803 }
804 
805 QDomElement QgsLayout::writeXml( QDomDocument &document, const QgsReadWriteContext &context ) const
806 {
807  QDomElement element = document.createElement( QStringLiteral( "Layout" ) );
808  auto save = [&]( const QgsLayoutSerializableObject * object )->bool
809  {
810  return object->writeXml( element, document, context );
811  };
812  save( &mSnapper );
813  save( &mGridSettings );
814  save( mPageCollection.get() );
815 
816  //save items except paper items and frame items (they are saved with the corresponding multiframe)
817  const QList<QGraphicsItem *> itemList = items();
818  for ( const QGraphicsItem *graphicsItem : itemList )
819  {
820  if ( const QgsLayoutItem *item = dynamic_cast< const QgsLayoutItem *>( graphicsItem ) )
821  {
822  if ( item->type() == QgsLayoutItemRegistry::LayoutPage )
823  continue;
824 
825  item->writeXml( element, document, context );
826  }
827  }
828 
829  //save multiframes
830  for ( QgsLayoutMultiFrame *mf : mMultiFrames )
831  {
832  mf->writeXml( element, document, context );
833  }
834 
835  writeXmlLayoutSettings( element, document, context );
836  return element;
837 }
838 
839 bool QgsLayout::readXmlLayoutSettings( const QDomElement &layoutElement, const QDomDocument &, const QgsReadWriteContext & )
840 {
841  mCustomProperties.readXml( layoutElement );
842  setUnits( QgsUnitTypes::decodeLayoutUnit( layoutElement.attribute( QStringLiteral( "units" ) ) ) );
843  mWorldFileMapId = layoutElement.attribute( QStringLiteral( "worldFileMap" ) );
844  mRenderContext->setDpi( layoutElement.attribute( QStringLiteral( "printResolution" ), QStringLiteral( "300" ) ).toDouble() );
845  emit changed();
846 
847  return true;
848 }
849 
850 void QgsLayout::addLayoutItemPrivate( QgsLayoutItem *item )
851 {
852  addItem( item );
853  updateBounds();
854  mItemsModel->rebuildZList();
855 }
856 
857 void QgsLayout::removeLayoutItemPrivate( QgsLayoutItem *item )
858 {
859  mItemsModel->setItemRemoved( item );
860  // small chance that item is still in a scene - the model may have
861  // rejected the removal for some reason. This is probably not necessary,
862  // but can't hurt...
863  if ( item->scene() )
864  removeItem( item );
865 #if 0 //TODO
866  emit itemRemoved( item );
867 #endif
868  item->cleanup();
869  item->deleteLater();
870 }
871 
872 void QgsLayout::deleteAndRemoveMultiFrames()
873 {
874  qDeleteAll( mMultiFrames );
875  mMultiFrames.clear();
876 }
877 
878 QPointF QgsLayout::minPointFromXml( const QDomElement &elem ) const
879 {
880  double minX = std::numeric_limits<double>::max();
881  double minY = std::numeric_limits<double>::max();
882  const QDomNodeList itemList = elem.elementsByTagName( QStringLiteral( "LayoutItem" ) );
883  bool found = false;
884  for ( int i = 0; i < itemList.size(); ++i )
885  {
886  const QDomElement currentItemElem = itemList.at( i ).toElement();
887 
888  QgsLayoutPoint pos = QgsLayoutPoint::decodePoint( currentItemElem.attribute( QStringLiteral( "position" ) ) );
889  QPointF layoutPoint = convertToLayoutUnits( pos );
890 
891  minX = std::min( minX, layoutPoint.x() );
892  minY = std::min( minY, layoutPoint.y() );
893  found = true;
894  }
895  return found ? QPointF( minX, minY ) : QPointF( 0, 0 );
896 }
897 
898 void QgsLayout::updateZValues( const bool addUndoCommands )
899 {
900  int counter = mItemsModel->zOrderListSize();
901  const QList<QgsLayoutItem *> zOrderList = mItemsModel->zOrderList();
902 
903  if ( addUndoCommands )
904  {
905  mUndoStack->beginMacro( tr( "Change Item Stacking" ) );
906  }
907  for ( QgsLayoutItem *currentItem : zOrderList )
908  {
909  if ( currentItem )
910  {
911  if ( addUndoCommands )
912  {
913  mUndoStack->beginCommand( currentItem, QString() );
914  }
915  currentItem->setZValue( counter );
916  if ( addUndoCommands )
917  {
918  mUndoStack->endCommand();
919  }
920  }
921  --counter;
922  }
923  if ( addUndoCommands )
924  {
925  mUndoStack->endMacro();
926  }
927 }
928 
929 bool QgsLayout::readXml( const QDomElement &layoutElement, const QDomDocument &document, const QgsReadWriteContext &context )
930 {
931  if ( layoutElement.nodeName() != QStringLiteral( "Layout" ) )
932  {
933  return false;
934  }
935 
936  auto restore = [&]( QgsLayoutSerializableObject * object )->bool
937  {
938  return object->readXml( layoutElement, document, context );
939  };
940 
941  blockSignals( true ); // defer changed signal to end
942  readXmlLayoutSettings( layoutElement, document, context );
943  blockSignals( false );
944 
945  restore( mPageCollection.get() );
946  restore( &mSnapper );
947  restore( &mGridSettings );
948  addItemsFromXml( layoutElement, document, context );
949 
950  emit changed();
951 
952  return true;
953 }
954 
955 QList< QgsLayoutItem * > QgsLayout::addItemsFromXml( const QDomElement &parentElement, const QDomDocument &document, const QgsReadWriteContext &context, QPointF *position, bool pasteInPlace )
956 {
957  QList< QgsLayoutItem * > newItems;
958  QList< QgsLayoutMultiFrame * > newMultiFrames;
959 
960  //if we are adding items to a layout which already contains items, we need to make sure
961  //these items are placed at the top of the layout and that zValues are not duplicated
962  //so, calculate an offset which needs to be added to the zValue of created items
963  int zOrderOffset = mItemsModel->zOrderListSize();
964 
965  QPointF pasteShiftPos;
966  int pageNumber = -1;
967  if ( position )
968  {
969  //If we are placing items relative to a certain point, then calculate how much we need
970  //to shift the items by so that they are placed at this point
971  //First, calculate the minimum position from the xml
972  QPointF minItemPos = minPointFromXml( parentElement );
973  //next, calculate how much each item needs to be shifted from its original position
974  //so that it's placed at the correct relative position
975  pasteShiftPos = *position - minItemPos;
976  if ( pasteInPlace )
977  {
978  pageNumber = mPageCollection->pageNumberForPoint( *position );
979  }
980  }
981  // multiframes
982 
983  //TODO - fix this. pasting multiframe frame items has no effect
984  const QDomNodeList multiFrameList = parentElement.elementsByTagName( QStringLiteral( "LayoutMultiFrame" ) );
985  for ( int i = 0; i < multiFrameList.size(); ++i )
986  {
987  const QDomElement multiFrameElem = multiFrameList.at( i ).toElement();
988  const int itemType = multiFrameElem.attribute( QStringLiteral( "type" ) ).toInt();
989  std::unique_ptr< QgsLayoutMultiFrame > mf( QgsApplication::layoutItemRegistry()->createMultiFrame( itemType, this ) );
990  if ( !mf )
991  {
992  // e.g. plugin based item which is no longer available
993  continue;
994  }
995  mf->readXml( multiFrameElem, document, context );
996 
997 #if 0 //TODO?
998  mf->setCreateUndoCommands( true );
999 #endif
1000 
1001  QgsLayoutMultiFrame *m = mf.get();
1002  this->addMultiFrame( mf.release() );
1003 
1004  //offset z values for frames
1005  //TODO - fix this after fixing multiframe item paste
1006  /*for ( int frameIdx = 0; frameIdx < mf->frameCount(); ++frameIdx )
1007  {
1008  QgsLayoutItemFrame * frame = mf->frame( frameIdx );
1009  frame->setZValue( frame->zValue() + zOrderOffset );
1010 
1011  // also need to shift frames according to position/pasteInPlacePt
1012  }*/
1013  newMultiFrames << m;
1014  }
1015 
1016  const QDomNodeList layoutItemList = parentElement.childNodes();
1017  for ( int i = 0; i < layoutItemList.size(); ++i )
1018  {
1019  const QDomElement currentItemElem = layoutItemList.at( i ).toElement();
1020  if ( currentItemElem.nodeName() != QStringLiteral( "LayoutItem" ) )
1021  continue;
1022 
1023  const int itemType = currentItemElem.attribute( QStringLiteral( "type" ) ).toInt();
1024  std::unique_ptr< QgsLayoutItem > item( QgsApplication::layoutItemRegistry()->createItem( itemType, this ) );
1025  if ( !item )
1026  {
1027  // e.g. plugin based item which is no longer available
1028  continue;
1029  }
1030 
1031  item->readXml( currentItemElem, document, context );
1032  if ( position )
1033  {
1034  if ( pasteInPlace )
1035  {
1036  QgsLayoutPoint posOnPage = QgsLayoutPoint::decodePoint( currentItemElem.attribute( QStringLiteral( "positionOnPage" ) ) );
1037  item->attemptMove( posOnPage, true, false, pageNumber );
1038  }
1039  else
1040  {
1041  item->attemptMoveBy( pasteShiftPos.x(), pasteShiftPos.y() );
1042  }
1043  }
1044 
1045  QgsLayoutItem *layoutItem = item.get();
1046  addLayoutItem( item.release() );
1047  layoutItem->setZValue( layoutItem->zValue() + zOrderOffset );
1048  newItems << layoutItem;
1049  }
1050 
1051  // we now allow items to "post-process", e.g. if they need to setup connections
1052  // to other items in the layout, which may not have existed at the time the
1053  // item's state was restored. E.g. a scalebar may have been restored before the map
1054  // it is linked to
1055  for ( QgsLayoutItem *item : qgis::as_const( newItems ) )
1056  {
1057  item->finalizeRestoreFromXml();
1058  }
1059  for ( QgsLayoutMultiFrame *mf : qgis::as_const( newMultiFrames ) )
1060  {
1061  mf->finalizeRestoreFromXml();
1062  }
1063 
1064  for ( QgsLayoutItem *item : qgis::as_const( newItems ) )
1065  {
1066  item->mTemplateUuid.clear();
1067  }
1068  for ( QgsLayoutMultiFrame *mf : qgis::as_const( newMultiFrames ) )
1069  {
1070  mf->mTemplateUuid.clear();
1071  }
1072 
1073  //Since this function adds items in an order which isn't the z-order, and each item is added to end of
1074  //z order list in turn, it will now be inconsistent with the actual order of items in the scene.
1075  //Make sure z order list matches the actual order of items in the scene.
1076  mItemsModel->rebuildZList();
1077 
1078  return newItems;
1079 }
1080 
1082 {
1083  setSceneRect( layoutBounds( false, 0.05 ) );
1084 }
void setDirty(bool b=true)
Flag the project as dirty (modified).
Definition: qgsproject.cpp:460
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 a nullptr if a matching multif...
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:683
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:735
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:929
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: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:955
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:761
void updateBounds()
Updates the scene bounds of the layout.
Definition: qgslayout.cpp:1081
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:583
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:898
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:786
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:606
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:578
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:511
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:588
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:553
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:537
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:805
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:730
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()
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: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 a nullptr if a matching ite...
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:569
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