QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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"
32 
34  : mProject( project )
35  , mRenderContext( new QgsLayoutRenderContext( this ) )
36  , mReportContext( new QgsLayoutReportContext( this ) )
37  , mSnapper( QgsLayoutSnapper( this ) )
38  , mGridSettings( this )
39  , mPageCollection( new QgsLayoutPageCollection( this ) )
40  , mUndoStack( new QgsLayoutUndoStack( this ) )
41 {
42  // just to make sure - this should be the default, but maybe it'll change in some future Qt version...
43  setBackgroundBrush( Qt::NoBrush );
44  mItemsModel.reset( new QgsLayoutModel( this ) );
45 }
46 
48 {
49  // no need for undo commands when we're destroying the layout
50  mUndoStack->blockCommands( true );
51 
52  deleteAndRemoveMultiFrames();
53 
54  // make sure that all layout items are removed before
55  // this class is deconstructed - to avoid segfaults
56  // when layout items access in destructor layout that isn't valid anymore
57 
58  // since deletion of some item types (e.g. groups) trigger deletion
59  // of other items, we have to do this careful, one at a time...
60  QList<QGraphicsItem *> itemList = items();
61  bool deleted = true;
62  while ( deleted )
63  {
64  deleted = false;
65  for ( QGraphicsItem *item : qgis::as_const( itemList ) )
66  {
67  if ( dynamic_cast< QgsLayoutItem * >( item ) && !dynamic_cast< QgsLayoutItemPage *>( item ) )
68  {
69  delete item;
70  deleted = true;
71  break;
72  }
73  }
74  itemList = items();
75  }
76 
77  mItemsModel.reset(); // manually delete, so we can control order of destruction
78 }
79 
81 {
82  QDomDocument currentDoc;
83 
84  QgsReadWriteContext context;
85  QDomElement elem = writeXml( currentDoc, context );
86  currentDoc.appendChild( elem );
87 
88  std::unique_ptr< QgsLayout > newLayout = qgis::make_unique< QgsLayout >( mProject );
89  bool ok = false;
90  newLayout->loadFromTemplate( currentDoc, context, true, &ok );
91  if ( !ok )
92  {
93  return nullptr;
94  }
95 
96  return newLayout.release();
97 }
98 
100 {
101  // default to a A4 landscape page
102  QgsLayoutItemPage *page = new QgsLayoutItemPage( this );
104  mPageCollection->addPage( page );
105  mUndoStack->stack()->clear();
106 }
107 
109 {
110  deleteAndRemoveMultiFrames();
111 
112  //delete all non paper items
113  const QList<QGraphicsItem *> itemList = items();
114  for ( QGraphicsItem *item : itemList )
115  {
116  QgsLayoutItem *cItem = dynamic_cast<QgsLayoutItem *>( item );
117  QgsLayoutItemPage *pItem = dynamic_cast<QgsLayoutItemPage *>( item );
118  if ( cItem && !pItem )
119  {
120  removeLayoutItemPrivate( cItem );
121  }
122  }
123  mItemsModel->clear();
124 
125  mPageCollection->clear();
126  mUndoStack->stack()->clear();
127 }
128 
130 {
131  return mProject;
132 }
133 
135 {
136  return mItemsModel.get();
137 }
138 
139 QList<QgsLayoutItem *> QgsLayout::selectedLayoutItems( const bool includeLockedItems )
140 {
141  QList<QgsLayoutItem *> layoutItemList;
142 
143  const QList<QGraphicsItem *> graphicsItemList = selectedItems();
144  for ( QGraphicsItem *item : graphicsItemList )
145  {
146  QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item );
147  if ( layoutItem && ( includeLockedItems || !layoutItem->isLocked() ) )
148  {
149  layoutItemList.push_back( layoutItem );
150  }
151  }
152 
153  return layoutItemList;
154 }
155 
157 {
158  whileBlocking( this )->deselectAll();
159  if ( item )
160  {
161  item->setSelected( true );
162  }
163  emit selectedItemChanged( item );
164 }
165 
167 {
168  //we can't use QGraphicsScene::clearSelection, as that emits no signals
169  //and we don't know which items are being deselected
170  //accordingly, we can't inform the layout model of selection changes
171  //instead, do the clear selection manually...
172  const QList<QGraphicsItem *> selectedItemList = selectedItems();
173  for ( QGraphicsItem *item : selectedItemList )
174  {
175  if ( QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item ) )
176  {
177  layoutItem->setSelected( false );
178  }
179  }
180  emit selectedItemChanged( nullptr );
181 }
182 
183 bool QgsLayout::raiseItem( QgsLayoutItem *item, bool deferUpdate )
184 {
185  //model handles reordering items
186  bool result = mItemsModel->reorderItemUp( item );
187  if ( result && !deferUpdate )
188  {
189  //update all positions
190  updateZValues();
191  update();
192  }
193  return result;
194 }
195 
196 bool QgsLayout::lowerItem( QgsLayoutItem *item, bool deferUpdate )
197 {
198  //model handles reordering items
199  bool result = mItemsModel->reorderItemDown( item );
200  if ( result && !deferUpdate )
201  {
202  //update all positions
203  updateZValues();
204  update();
205  }
206  return result;
207 }
208 
209 bool QgsLayout::moveItemToTop( QgsLayoutItem *item, bool deferUpdate )
210 {
211  //model handles reordering items
212  bool result = mItemsModel->reorderItemToTop( item );
213  if ( result && !deferUpdate )
214  {
215  //update all positions
216  updateZValues();
217  update();
218  }
219  return result;
220 }
221 
222 bool QgsLayout::moveItemToBottom( QgsLayoutItem *item, bool deferUpdate )
223 {
224  //model handles reordering items
225  bool result = mItemsModel->reorderItemToBottom( item );
226  if ( result && !deferUpdate )
227  {
228  //update all positions
229  updateZValues();
230  update();
231  }
232  return result;
233 }
234 
235 QgsLayoutItem *QgsLayout::itemByUuid( const QString &uuid, bool includeTemplateUuids ) const
236 {
237  QList<QgsLayoutItem *> itemList;
238  layoutItems( itemList );
239  for ( QgsLayoutItem *item : qgis::as_const( itemList ) )
240  {
241  if ( item->uuid() == uuid )
242  return item;
243  else if ( includeTemplateUuids && item->mTemplateUuid == uuid )
244  return item;
245  }
246 
247  return nullptr;
248 }
249 
250 QgsLayoutItem *QgsLayout::itemByTemplateUuid( const QString &uuid ) const
251 {
252  QList<QgsLayoutItem *> itemList;
253  layoutItems( itemList );
254  for ( QgsLayoutItem *item : qgis::as_const( itemList ) )
255  {
256  if ( item->mTemplateUuid == uuid )
257  return item;
258  }
259 
260  return nullptr;
261 }
262 
263 QgsLayoutItem *QgsLayout::itemById( const QString &id ) const
264 {
265  const QList<QGraphicsItem *> itemList = items();
266  for ( QGraphicsItem *item : itemList )
267  {
268  QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item );
269  if ( layoutItem && layoutItem->id() == id )
270  {
271  return layoutItem;
272  }
273  }
274  return nullptr;
275 }
276 
277 QgsLayoutMultiFrame *QgsLayout::multiFrameByUuid( const QString &uuid, bool includeTemplateUuids ) const
278 {
279  for ( QgsLayoutMultiFrame *mf : mMultiFrames )
280  {
281  if ( mf->uuid() == uuid )
282  return mf;
283  else if ( includeTemplateUuids && mf->mTemplateUuid == uuid )
284  return mf;
285  }
286 
287  return nullptr;
288 }
289 
290 QgsLayoutItem *QgsLayout::layoutItemAt( QPointF position, const bool ignoreLocked ) const
291 {
292  return layoutItemAt( position, nullptr, ignoreLocked );
293 }
294 
295 QgsLayoutItem *QgsLayout::layoutItemAt( QPointF position, const QgsLayoutItem *belowItem, const bool ignoreLocked ) const
296 {
297  //get a list of items which intersect the specified position, in descending z order
298  const QList<QGraphicsItem *> itemList = items( position, Qt::IntersectsItemShape, Qt::DescendingOrder );
299 
300  bool foundBelowItem = false;
301  for ( QGraphicsItem *graphicsItem : itemList )
302  {
303  QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( graphicsItem );
304  QgsLayoutItemPage *paperItem = dynamic_cast<QgsLayoutItemPage *>( layoutItem );
305  if ( layoutItem && !paperItem )
306  {
307  // If we are not checking for a an item below a specified item, or if we've
308  // already found that item, then we've found our target
309  if ( ( ! belowItem || foundBelowItem ) && ( !ignoreLocked || !layoutItem->isLocked() ) )
310  {
311  return layoutItem;
312  }
313  else
314  {
315  if ( layoutItem == belowItem )
316  {
317  //Target item is next in list
318  foundBelowItem = true;
319  }
320  }
321  }
322  }
323  return nullptr;
324 }
325 
327 {
328  return mRenderContext->measurementConverter().convert( measurement, mUnits ).length();
329 }
330 
332 {
333  return mRenderContext->measurementConverter().convert( size, mUnits ).toQSizeF();
334 }
335 
336 QPointF QgsLayout::convertToLayoutUnits( const QgsLayoutPoint &point ) const
337 {
338  return mRenderContext->measurementConverter().convert( point, mUnits ).toQPointF();
339 }
340 
342 {
343  return mRenderContext->measurementConverter().convert( QgsLayoutMeasurement( length, mUnits ), unit );
344 }
345 
347 {
348  return mRenderContext->measurementConverter().convert( QgsLayoutSize( size.width(), size.height(), mUnits ), unit );
349 }
350 
352 {
353  return mRenderContext->measurementConverter().convert( QgsLayoutPoint( point.x(), point.y(), mUnits ), unit );
354 }
355 
357 {
358  return *mRenderContext;
359 }
360 
362 {
363  return *mRenderContext;
364 }
365 
367 {
368  return *mReportContext;
369 }
370 
372 {
373  return *mReportContext;
374 }
375 
377 {
378  mGridSettings.loadFromSettings();
379  mPageCollection->redraw();
380 }
381 
383 {
384  return mPageCollection->guides();
385 }
386 
388 {
389  return mPageCollection->guides();
390 }
391 
393 {
397  if ( mReportContext->layer() )
398  context.appendScope( QgsExpressionContextUtils::layerScope( mReportContext->layer() ) );
399 
401  return context;
402 }
403 
404 void QgsLayout::setCustomProperty( const QString &key, const QVariant &value )
405 {
406  mCustomProperties.setValue( key, value );
407 
408  if ( key.startsWith( QLatin1String( "variable" ) ) )
409  emit variablesChanged();
410 }
411 
412 QVariant QgsLayout::customProperty( const QString &key, const QVariant &defaultValue ) const
413 {
414  return mCustomProperties.value( key, defaultValue );
415 }
416 
417 void QgsLayout::removeCustomProperty( const QString &key )
418 {
419  mCustomProperties.remove( key );
420 }
421 
422 QStringList QgsLayout::customProperties() const
423 {
424  return mCustomProperties.keys();
425 }
426 
428 {
429  // prefer explicitly set reference map
430  if ( QgsLayoutItemMap *map = qobject_cast< QgsLayoutItemMap * >( itemByUuid( mWorldFileMapId ) ) )
431  return map;
432 
433  // else try to find largest map
434  QList< QgsLayoutItemMap * > maps;
435  layoutItems( maps );
436  QgsLayoutItemMap *largestMap = nullptr;
437  double largestMapArea = 0;
438  for ( QgsLayoutItemMap *map : qgis::as_const( maps ) )
439  {
440  double area = map->rect().width() * map->rect().height();
441  if ( area > largestMapArea )
442  {
443  largestMapArea = area;
444  largestMap = map;
445  }
446  }
447  return largestMap;
448 }
449 
451 {
452  mWorldFileMapId = map ? map->uuid() : QString();
453  mProject->setDirty( true );
454 }
455 
457 {
458  return mPageCollection.get();
459 }
460 
462 {
463  return mPageCollection.get();
464 }
465 
466 QRectF QgsLayout::layoutBounds( bool ignorePages, double margin ) const
467 {
468  //start with an empty rectangle
469  QRectF bounds;
470 
471  //add all layout items and pages which are in the layout
472  Q_FOREACH ( const QGraphicsItem *item, items() )
473  {
474  const QgsLayoutItem *layoutItem = dynamic_cast<const QgsLayoutItem *>( item );
475  if ( !layoutItem )
476  continue;
477 
478  bool isPage = layoutItem->type() == QgsLayoutItemRegistry::LayoutPage;
479  if ( !isPage || !ignorePages )
480  {
481  //expand bounds with current item's bounds
482  QRectF itemBounds;
483  if ( isPage )
484  {
485  // for pages we only consider the item's rect - not the bounding rect
486  // as the bounding rect contains extra padding
487  itemBounds = layoutItem->mapToScene( layoutItem->rect() ).boundingRect();
488  }
489  else
490  itemBounds = item->sceneBoundingRect();
491 
492  if ( bounds.isValid() )
493  bounds = bounds.united( itemBounds );
494  else
495  bounds = itemBounds;
496  }
497  }
498 
499  if ( bounds.isValid() && margin > 0.0 )
500  {
501  //finally, expand bounds out by specified margin of page size
502  double maxWidth = mPageCollection->maximumPageWidth();
503  bounds.adjust( -maxWidth * margin, -maxWidth * margin, maxWidth * margin, maxWidth * margin );
504  }
505 
506  return bounds;
507 
508 }
509 
510 QRectF QgsLayout::pageItemBounds( int page, bool visibleOnly ) const
511 {
512  //start with an empty rectangle
513  QRectF bounds;
514 
515  //add all QgsLayoutItems on page
516  const QList<QGraphicsItem *> itemList = items();
517  for ( QGraphicsItem *item : itemList )
518  {
519  const QgsLayoutItem *layoutItem = dynamic_cast<const QgsLayoutItem *>( item );
520  if ( layoutItem && layoutItem->type() != QgsLayoutItemRegistry::LayoutPage && layoutItem->page() == page )
521  {
522  if ( visibleOnly && !layoutItem->isVisible() )
523  continue;
524 
525  //expand bounds with current item's bounds
526  if ( bounds.isValid() )
527  bounds = bounds.united( item->sceneBoundingRect() );
528  else
529  bounds = item->sceneBoundingRect();
530  }
531  }
532 
533  return bounds;
534 }
535 
537 {
538  addLayoutItemPrivate( item );
539  QString undoText;
540  if ( QgsLayoutItemAbstractMetadata *metadata = QgsApplication::layoutItemRegistry()->itemMetadata( item->type() ) )
541  {
542  undoText = tr( "Create %1" ).arg( metadata->visibleName() );
543  }
544  else
545  {
546  undoText = tr( "Create Item" );
547  }
548  if ( !mUndoStack->isBlocked() )
549  mUndoStack->push( new QgsLayoutItemAddItemCommand( item, undoText ) );
550 }
551 
553 {
554  std::unique_ptr< QgsLayoutItemDeleteUndoCommand > deleteCommand;
555  if ( !mUndoStack->isBlocked() )
556  {
557  mUndoStack->beginMacro( tr( "Delete Items" ) );
558  deleteCommand.reset( new QgsLayoutItemDeleteUndoCommand( item, tr( "Delete Item" ) ) );
559  }
560  removeLayoutItemPrivate( item );
561  if ( deleteCommand )
562  {
563  mUndoStack->push( deleteCommand.release() );
564  mUndoStack->endMacro();
565  }
566 }
567 
569 {
570  if ( !multiFrame )
571  return;
572 
573  if ( !mMultiFrames.contains( multiFrame ) )
574  mMultiFrames << multiFrame;
575 }
576 
578 {
579  mMultiFrames.removeAll( multiFrame );
580 }
581 
582 QList<QgsLayoutMultiFrame *> QgsLayout::multiFrames() const
583 {
584  return mMultiFrames;
585 }
586 
587 bool QgsLayout::saveAsTemplate( const QString &path, const QgsReadWriteContext &context ) const
588 {
589  QFile templateFile( path );
590  if ( !templateFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
591  {
592  return false;
593  }
594 
595  QDomDocument saveDocument;
596  QDomElement elem = writeXml( saveDocument, context );
597  saveDocument.appendChild( elem );
598 
599  if ( templateFile.write( saveDocument.toByteArray() ) == -1 )
600  return false;
601 
602  return true;
603 }
604 
605 QList< QgsLayoutItem * > QgsLayout::loadFromTemplate( const QDomDocument &document, const QgsReadWriteContext &context, bool clearExisting, bool *ok )
606 {
607  if ( ok )
608  *ok = false;
609 
610  QList< QgsLayoutItem * > result;
611 
612  if ( clearExisting )
613  {
614  clear();
615  }
616 
617  QDomDocument doc;
618 
619  // If this is a 2.x composition template, convert it to a layout template
621  {
622  doc = QgsCompositionConverter::convertCompositionTemplate( document, mProject );
623  }
624  else
625  {
626  doc = document;
627  }
628 
629  // remove all uuid attributes since we don't want duplicates UUIDS
630  QDomNodeList itemsNodes = doc.elementsByTagName( QStringLiteral( "LayoutItem" ) );
631  for ( int i = 0; i < itemsNodes.count(); ++i )
632  {
633  QDomNode itemNode = itemsNodes.at( i );
634  if ( itemNode.isElement() )
635  {
636  itemNode.toElement().removeAttribute( QStringLiteral( "uuid" ) );
637  }
638  }
639  QDomNodeList multiFrameNodes = doc.elementsByTagName( QStringLiteral( "LayoutMultiFrame" ) );
640  for ( int i = 0; i < multiFrameNodes.count(); ++i )
641  {
642  QDomNode multiFrameNode = multiFrameNodes.at( i );
643  if ( multiFrameNode.isElement() )
644  {
645  multiFrameNode.toElement().removeAttribute( QStringLiteral( "uuid" ) );
646  QDomNodeList frameNodes = multiFrameNode.toElement().elementsByTagName( QStringLiteral( "childFrame" ) );
647  QDomNode itemNode = frameNodes.at( i );
648  if ( itemNode.isElement() )
649  {
650  itemNode.toElement().removeAttribute( QStringLiteral( "uuid" ) );
651  }
652  }
653  }
654 
655  //read general settings
656  if ( clearExisting )
657  {
658  QDomElement layoutElem = doc.documentElement();
659  if ( layoutElem.isNull() )
660  {
661  return result;
662  }
663 
664  bool loadOk = readXml( layoutElem, doc, context );
665  if ( !loadOk )
666  {
667  return result;
668  }
669  layoutItems( result );
670  }
671  else
672  {
673  result = addItemsFromXml( doc.documentElement(), doc, context );
674  }
675 
676  if ( ok )
677  *ok = true;
678 
679  return result;
680 }
681 
683 {
684  return mUndoStack.get();
685 }
686 
688 {
689  return mUndoStack.get();
690 }
691 
694 {
695  public:
696 
697  QgsLayoutUndoCommand( QgsLayout *layout, const QString &text, int id, QUndoCommand *parent SIP_TRANSFERTHIS = nullptr )
698  : QgsAbstractLayoutUndoCommand( text, id, parent )
699  , mLayout( layout )
700  {}
701 
702  protected:
703 
704  void saveState( QDomDocument &stateDoc ) const override
705  {
706  stateDoc.clear();
707  QDomElement documentElement = stateDoc.createElement( QStringLiteral( "UndoState" ) );
708  mLayout->writeXmlLayoutSettings( documentElement, stateDoc, QgsReadWriteContext() );
709  stateDoc.appendChild( documentElement );
710  }
711 
712  void restoreState( QDomDocument &stateDoc ) override
713  {
714  if ( !mLayout )
715  {
716  return;
717  }
718 
719  mLayout->readXmlLayoutSettings( stateDoc.documentElement(), stateDoc, QgsReadWriteContext() );
720  mLayout->project()->setDirty( true );
721  }
722 
723  private:
724 
725  QgsLayout *mLayout = nullptr;
726 };
728 
729 QgsAbstractLayoutUndoCommand *QgsLayout::createCommand( const QString &text, int id, QUndoCommand *parent )
730 {
731  return new QgsLayoutUndoCommand( this, text, id, parent );
732 }
733 
734 QgsLayoutItemGroup *QgsLayout::groupItems( const QList<QgsLayoutItem *> &items )
735 {
736  if ( items.size() < 2 )
737  {
738  //not enough items for a group
739  return nullptr;
740  }
741 
742  mUndoStack->beginMacro( tr( "Group Items" ) );
743  std::unique_ptr< QgsLayoutItemGroup > itemGroup( new QgsLayoutItemGroup( this ) );
744  for ( QgsLayoutItem *item : items )
745  {
746  itemGroup->addItem( item );
747  }
748  QgsLayoutItemGroup *returnGroup = itemGroup.get();
749  addLayoutItem( itemGroup.release() );
750 
751  std::unique_ptr< QgsLayoutItemGroupUndoCommand > c( new QgsLayoutItemGroupUndoCommand( QgsLayoutItemGroupUndoCommand::Grouped, returnGroup, this, tr( "Group Items" ) ) );
752  mUndoStack->push( c.release() );
753  mProject->setDirty( true );
754 
755  mUndoStack->endMacro();
756 
757  return returnGroup;
758 }
759 
760 QList<QgsLayoutItem *> QgsLayout::ungroupItems( QgsLayoutItemGroup *group )
761 {
762  QList<QgsLayoutItem *> ungroupedItems;
763  if ( !group )
764  {
765  return ungroupedItems;
766  }
767 
768  mUndoStack->beginMacro( tr( "Ungroup Items" ) );
769  // Call this before removing group items so it can keep note
770  // of contents
771  std::unique_ptr< QgsLayoutItemGroupUndoCommand > c( new QgsLayoutItemGroupUndoCommand( QgsLayoutItemGroupUndoCommand::Ungrouped, group, this, tr( "Ungroup Items" ) ) );
772  mUndoStack->push( c.release() );
773 
774  mProject->setDirty( true );
775 
776  ungroupedItems = group->items();
777  group->removeItems();
778 
779  removeLayoutItem( group );
780  mUndoStack->endMacro();
781 
782  return ungroupedItems;
783 }
784 
786 {
787  mUndoStack->blockCommands( true );
788  mPageCollection->beginPageSizeChange();
789  emit refreshed();
790  mPageCollection->reflow();
791  mPageCollection->endPageSizeChange();
792  mUndoStack->blockCommands( false );
793  update();
794 }
795 
796 void QgsLayout::writeXmlLayoutSettings( QDomElement &element, QDomDocument &document, const QgsReadWriteContext & ) const
797 {
798  mCustomProperties.writeXml( element, document );
799  element.setAttribute( QStringLiteral( "units" ), QgsUnitTypes::encodeUnit( mUnits ) );
800  element.setAttribute( QStringLiteral( "worldFileMap" ), mWorldFileMapId );
801  element.setAttribute( QStringLiteral( "printResolution" ), mRenderContext->dpi() );
802 }
803 
804 QDomElement QgsLayout::writeXml( QDomDocument &document, const QgsReadWriteContext &context ) const
805 {
806  QDomElement element = document.createElement( QStringLiteral( "Layout" ) );
807  auto save = [&]( const QgsLayoutSerializableObject * object )->bool
808  {
809  return object->writeXml( element, document, context );
810  };
811  save( &mSnapper );
812  save( &mGridSettings );
813  save( mPageCollection.get() );
814 
815  //save items except paper items and frame items (they are saved with the corresponding multiframe)
816  const QList<QGraphicsItem *> itemList = items();
817  for ( const QGraphicsItem *graphicsItem : itemList )
818  {
819  if ( const QgsLayoutItem *item = dynamic_cast< const QgsLayoutItem *>( graphicsItem ) )
820  {
821  if ( item->type() == QgsLayoutItemRegistry::LayoutPage )
822  continue;
823 
824  item->writeXml( element, document, context );
825  }
826  }
827 
828  //save multiframes
829  for ( QgsLayoutMultiFrame *mf : mMultiFrames )
830  {
831  if ( mf->frameCount() > 0 )
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:441
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).
QList< QgsLayoutItem * > items() const
Returns a list of items contained by the group.
void setSelectedItem(QgsLayoutItem *item)
Clears any selected items and sets item as the current selection.
Definition: qgslayout.cpp:156
bool raiseItem(QgsLayoutItem *item, bool deferUpdate=false)
Raises an item up the z-order.
Definition: qgslayout.cpp:183
QgsLayoutGuideCollection & guides()
Returns a reference to the layout&#39;s guide collection, which manages page snap guides.
Definition: qgslayout.cpp:382
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.
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
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:682
friend class QgsLayoutModel
Definition: qgslayout.h:739
QList< QgsLayoutMultiFrame * > multiFrames() const
Returns a list of multi frames contained in the layout.
Definition: qgslayout.cpp:582
QRectF layoutBounds(bool ignorePages=false, double margin=0.0) const
Calculates the bounds of all non-gui items in the layout.
Definition: qgslayout.cpp:466
Stores information relating to the current reporting context for a layout.
QStringList customProperties() const
Returns list of keys stored in custom properties for the layout.
Definition: qgslayout.cpp:422
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.
void loadFromSettings()
Loads grid settings from the application layout settings.
double length() const
Returns the length of the measurement.
A container for grouping several QgsLayoutItems.
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:341
QgsLayoutItemMap * referenceMap() const
Returns the map item which will be used to generate corresponding world files when the layout is expo...
Definition: qgslayout.cpp:427
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:277
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.
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:760
void updateBounds()
Updates the scene bounds of the layout.
Definition: qgslayout.cpp:1081
const QgsLayoutMeasurementConverter & measurementConverter() const
Returns the layout measurement converter to be used in the layout.
QgsLayoutRenderContext & renderContext()
Returns a reference to the layout&#39;s render context, which stores information relating to the current ...
Definition: qgslayout.cpp:356
void remove(const QString &key)
Remove a key (entry) from the store.
QList< QgsLayoutItem * > loadFromTemplate(const QDomDocument &document, const QgsReadWriteContext &context, bool clearExisting=true, bool *ok=nullptr)
Load a layout template document.
Definition: qgslayout.cpp:605
Abstract base class for layout items with the ability to distribute the content to several frames (Qg...
void layoutItems(QList< T * > &itemList) const
Returns a list of layout items of a specific type.
Definition: qgslayout.h:121
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:235
This class provides a method of storing points, consisting of an x and y coordinate, for use in QGIS layouts.
void removeCustomProperty(const QString &key)
Remove a custom property from the layout.
Definition: qgslayout.cpp:417
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
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.
void deselectAll()
Clears any selected items in the layout.
Definition: qgslayout.cpp:166
QgsLayoutItem * itemById(const QString &id) const
Returns a layout item given its id.
Definition: qgslayout.cpp:263
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
QgsLayout * clone() const
Creates a clone of the layout.
Definition: qgslayout.cpp:80
QgsLayoutMeasurement convert(QgsLayoutMeasurement measurement, QgsUnitTypes::LayoutUnit targetUnits) const
Converts a measurement from one unit to another.
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:785
QgsLayoutPageCollection * pageCollection()
Returns a pointer to the layout&#39;s page collection, which stores and manages page items in the layout...
Definition: qgslayout.cpp:456
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
QStringList keys() const
Returns list of stored keys.
QList< QgsLayoutItem * > selectedLayoutItems(bool includeLockedItems=true)
Returns list of selected layout items.
Definition: qgslayout.cpp:139
bool saveAsTemplate(const QString &path, const QgsReadWriteContext &context) const
Saves the layout as a template at the given file path.
Definition: qgslayout.cpp:587
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:577
Stores and manages the snap guides used by a layout.
Reads and writes project states.
Definition: qgsproject.h:89
virtual QString uuid() const
Returns the item identification string.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:49
A manager for a collection of pages in a layout.
bool lowerItem(QgsLayoutItem *item, bool deferUpdate=false)
Lowers an item down the z-order.
Definition: qgslayout.cpp:196
static Q_INVOKABLE QString encodeUnit(QgsUnitTypes::DistanceUnit unit)
Encodes a distance unit to a string.
QVariant customProperty(const QString &key, const QVariant &defaultValue=QVariant()) const
Read a custom property from the layout.
Definition: qgslayout.cpp:412
QgsLayoutModel * itemsModel()
Returns the items model attached to the layout.
Definition: qgslayout.cpp:134
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:366
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:392
int page() const
Returns the page the item is currently on, with the first page returning 0.
bool moveItemToBottom(QgsLayoutItem *item, bool deferUpdate=false)
Lowers an item down to the bottom of the z-order.
Definition: qgslayout.cpp:222
void clear()
Clears the layout.
Definition: qgslayout.cpp:108
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:225
void removeLayoutItem(QgsLayoutItem *item)
Removes an item from the layout.
Definition: qgslayout.cpp:552
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.
void addLayoutItem(QgsLayoutItem *item)
Adds an item to the layout.
Definition: qgslayout.cpp:536
Stores information relating to the current rendering settings for 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:510
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:450
An interface for layout objects which can be stored and read from DOM elements.
~QgsLayout() override
Definition: qgslayout.cpp:47
void initializeDefaults()
Initializes an empty layout, e.g.
Definition: qgslayout.cpp:99
void setCustomProperty(const QString &key, const QVariant &value)
Set a custom property for the layout.
Definition: qgslayout.cpp:404
LayoutUnit
Layout measurement units.
Definition: qgsunittypes.h:124
QString id() const
Returns the item&#39;s ID name.
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:734
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:729
void setPageSize(const QgsLayoutSize &size)
Sets the size of the page.
A model for items attached to a layout.
virtual QDomElement writeXml(QDomDocument &document, const QgsReadWriteContext &context) const
Returns the layout&#39;s state encapsulated in a DOM element.
Definition: qgslayout.cpp:804
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.
bool isLocked() const
Returns true if the item is locked, and cannot be interacted with using the mouse.
This class provides a method of storing sizes, consisting of a width and height, for use in QGIS layo...
Definition: qgslayoutsize.h:40
void changed()
Is emitted when properties of the layout change.
bool moveItemToTop(QgsLayoutItem *item, bool deferUpdate=false)
Raises an item up to the top of the z-order.
Definition: qgslayout.cpp:209
void reloadSettings()
Refreshes the layout when global layout related options change.
Definition: qgslayout.cpp:376
void addMultiFrame(QgsLayoutMultiFrame *multiFrame)
Adds a multiFrame to the layout.
Definition: qgslayout.cpp:568
QgsLayout(QgsProject *project)
Construct a new layout linked to the specified project.
Definition: qgslayout.cpp:33
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:250
QgsProject * project() const
The project associated with the layout.
Definition: qgslayout.cpp:129
QgsVectorLayer * layer() const
Returns the vector layer associated with the layout&#39;s context.
QgsLayoutItem * layoutItemAt(QPointF position, bool ignoreLocked=false) const
Returns the topmost layout item at a specified position.
Definition: qgslayout.cpp:290
static QgsLayoutPoint decodePoint(const QString &string)
Decodes a point from a string.
double convertToLayoutUnits(QgsLayoutMeasurement measurement) const
Converts a measurement into the layout&#39;s native units.
Definition: qgslayout.cpp:326
Item representing the paper in a layout.
friend class QgsLayoutItemDeleteUndoCommand
Definition: qgslayout.h:735