QGIS API Documentation  2.99.0-Master (19b062c)
qgscomposition.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscomposition.cpp
3  -------------------
4  begin : January 2005
5  copyright : (C) 2005 by Radim Blazek
6  email : [email protected]
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 "qgscomposition.h"
18 #include "qgscomposerutils.h"
19 #include "qgscomposerarrow.h"
20 #include "qgscomposerpolygon.h"
21 #include "qgscomposerpolyline.h"
22 #include "qgscomposerframe.h"
23 #include "qgscomposerhtml.h"
24 #include "qgscomposerlabel.h"
25 #include "qgscomposerlegend.h"
26 #include "qgscomposermap.h"
27 #include "qgscomposermapoverview.h"
29 #include "qgscomposeritemgroup.h"
30 #include "qgscomposerpicture.h"
31 #include "qgscomposerscalebar.h"
32 #include "qgscomposershape.h"
33 #include "qgscomposermodel.h"
38 #include "qgsmapsettings.h"
39 #include "qgspaintenginehack.h"
40 #include "qgspaperitem.h"
41 #include "qgspathresolver.h"
42 #include "qgsproject.h"
43 #include "qgsreadwritecontext.h"
44 #include "qgsgeometry.h"
45 #include "qgsvectorlayer.h"
46 #include "qgsvectordataprovider.h"
47 #include "qgsexpression.h"
48 #include "qgssymbol.h"
49 #include "qgssymbollayerutils.h"
50 #include "qgslogger.h"
51 #include "qgssettings.h"
52 #include "qgsogrutils.h"
53 
54 #include <QDomDocument>
55 #include <QDomElement>
56 #include <QGraphicsRectItem>
57 #include <QGraphicsView>
58 #include <QPainter>
59 #include <QPrinter>
60 #include <QDir>
61 
62 #include <limits>
63 
64 #include "gdal.h"
65 #include "cpl_conv.h"
66 
68  : QGraphicsScene( nullptr )
69  , mProject( project )
70  , mAtlasComposition( this )
71 {
72  init();
73 }
74 
76 {
77  // these members should be ideally in constructor's initialization list, but now we have two constructors...
78  mPlotStyle = QgsComposition::Preview;
79  mPageWidth = 297;
80  mPageHeight = 210;
81  mSpaceBetweenPages = 10;
82  mPageStyleSymbol = nullptr;
83  mPrintAsRaster = false;
84  mGenerateWorldFile = false;
85  mUseAdvancedEffects = true;
86  mSnapToGrid = false;
87  mGridVisible = false;
88  mPagesVisible = true;
89  mSnapGridResolution = 0;
90  mSnapGridOffsetX = 0;
91  mSnapGridOffsetY = 0;
92  mAlignmentSnap = true;
93  mGuidesVisible = true;
94  mSmartGuides = true;
95  mSnapTolerance = 0;
96  mBoundingBoxesVisible = true;
97  mSelectionHandles = nullptr;
98  mActiveItemCommand = nullptr;
99  mActiveMultiFrameCommand = nullptr;
100  mAtlasMode = QgsComposition::AtlasOff;
101  mPreventCursorChange = false;
102  mItemsModel = nullptr;
103  mUndoStack = new QUndoStack();
104 
105  mResizeToContentsMarginTop = 0;
106  mResizeToContentsMarginRight = 0;
107  mResizeToContentsMarginBottom = 0;
108  mResizeToContentsMarginLeft = 0;
109 
110  //connect to atlas toggling on/off and coverage layer and feature changes
111  //to update data defined values
112  connect( &mAtlasComposition, &QgsAtlasComposition::toggled, this, [this] { refreshDataDefinedProperty(); } );
113  connect( &mAtlasComposition, &QgsAtlasComposition::coverageLayerChanged, this, [this] { refreshDataDefinedProperty(); } );
114  connect( &mAtlasComposition, &QgsAtlasComposition::featureChanged, this, [this] { refreshDataDefinedProperty(); } );
115  //also, refreshing composition triggers a recalculation of data defined properties
116  connect( this, &QgsComposition::refreshItemsTriggered, this, [ = ] { refreshDataDefinedProperty(); } );
117  //toggling atlas or changing coverage layer requires data defined expressions to be reprepared
118  connect( &mAtlasComposition, &QgsAtlasComposition::toggled, this, [this] { prepareAllDataDefinedExpressions(); } );
119  connect( &mAtlasComposition, &QgsAtlasComposition::coverageLayerChanged, this, [this] { prepareAllDataDefinedExpressions(); } );
120 
121  setBackgroundBrush( QColor( 215, 215, 215 ) );
122  createDefaultPageStyleSymbol();
123 
124  addPaperItem();
125 
126  updateBounds();
127 
128  //add mouse selection handles to composition, and initially hide
129  mSelectionHandles = new QgsComposerMouseHandles( this );
130  addItem( mSelectionHandles );
131  mSelectionHandles->hide();
132  mSelectionHandles->setZValue( 500 );
133 
134  mPrintResolution = 300; //hardcoded default
135 
136  //load default composition settings
137  loadDefaults();
138  loadSettings();
139 
140  mItemsModel = new QgsComposerModel( this );
141 }
142 
143 
145 {
146  removePaperItems();
147  deleteAndRemoveMultiFrames();
148 
149  // make sure that all composer items are removed before
150  // this class is deconstructed - to avoid segfaults
151  // when composer items access in destructor composition that isn't valid anymore
152  QList<QGraphicsItem *> itemList = items();
153  qDeleteAll( itemList );
154 
155  //order is important here - we need to delete model last so that all items have already
156  //been deleted. Deleting the undo stack will also delete any items which have been
157  //removed from the scene, so this needs to be done before deleting the model
158  delete mUndoStack;
159 
160  delete mActiveItemCommand;
161  delete mActiveMultiFrameCommand;
162  delete mPageStyleSymbol;
163  delete mItemsModel;
164 }
165 
167 {
168  return mProject;
169 }
170 
171 void QgsComposition::setName( const QString &name )
172 {
173  mName = name;
174  emit nameChanged( name );
175 }
176 
177 void QgsComposition::loadDefaults()
178 {
179  QgsSettings settings;
180  mSnapGridResolution = settings.value( QStringLiteral( "Composer/defaultSnapGridResolution" ), 10.0 ).toDouble();
181  mSnapGridOffsetX = settings.value( QStringLiteral( "Composer/defaultSnapGridOffsetX" ), 0 ).toDouble();
182  mSnapGridOffsetY = settings.value( QStringLiteral( "Composer/defaultSnapGridOffsetY" ), 0 ).toDouble();
183  mSnapTolerance = settings.value( QStringLiteral( "Composer/defaultSnapTolerancePixels" ), 5 ).toInt();
184 }
185 
187 {
188  setSceneRect( compositionBounds( false, 0.05 ) );
189 }
190 
192 {
193  emit refreshItemsTriggered();
194 }
195 
197 {
199  if ( item )
200  {
201  item->setSelected( true );
202  emit selectedItemChanged( item );
203  }
204 }
205 
207 {
208  //we can't use QGraphicsScene::clearSelection, as that emits no signals
209  //and we don't know which items are being deselected
210  //accordingly, we can't inform the composition model of selection changes
211  //instead, do the clear selection manually...
212  QList<QGraphicsItem *> selectedItemList = selectedItems();
213  QList<QGraphicsItem *>::iterator itemIter = selectedItemList.begin();
214 
215  for ( ; itemIter != selectedItemList.end(); ++itemIter )
216  {
217  QgsComposerItem *composerItem = dynamic_cast<QgsComposerItem *>( *itemIter );
218  if ( composerItem )
219  {
220  composerItem->setSelected( false );
221  }
222  }
223  emit selectedItemChanged( nullptr );
224 }
225 
227 {
229  const QgsExpressionContext *evalContext = context ? context : &scopedContext;
230 
231 
232  //updates data defined properties and redraws composition to match
233  if ( property == QgsComposerObject::NumPages || property == QgsComposerObject::AllProperties )
234  {
235  setNumPages( numPages() );
236  }
237  if ( property == QgsComposerObject::PaperWidth || property == QgsComposerObject::PaperHeight ||
240  {
241  refreshPageSize( evalContext );
242  }
243 }
244 
245 QRectF QgsComposition::compositionBounds( bool ignorePages, double margin ) const
246 {
247  //start with an empty rectangle
248  QRectF bounds;
249 
250  //add all QgsComposerItems and QgsPaperItems which are in the composition
251  QList<QGraphicsItem *> itemList = items();
252  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
253  for ( ; itemIt != itemList.end(); ++itemIt )
254  {
255  const QgsComposerItem *composerItem = dynamic_cast<const QgsComposerItem *>( *itemIt );
256  const QgsPaperItem *paperItem = dynamic_cast<const QgsPaperItem *>( *itemIt );
257  if ( ( composerItem && ( !paperItem || !ignorePages ) ) )
258  {
259  //expand bounds with current item's bounds
260  if ( bounds.isValid() )
261  bounds = bounds.united( ( *itemIt )->sceneBoundingRect() );
262  else
263  bounds = ( *itemIt )->sceneBoundingRect();
264  }
265  }
266 
267  if ( bounds.isValid() && margin > 0.0 )
268  {
269  //finally, expand bounds out by specified margin of page size
270  bounds.adjust( -mPageWidth * margin, -mPageWidth * margin, mPageWidth * margin, mPageWidth * margin );
271  }
272 
273  return bounds;
274 }
275 
276 QRectF QgsComposition::pageItemBounds( int pageNumber, bool visibleOnly ) const
277 {
278  //start with an empty rectangle
279  QRectF bounds;
280 
281  //add all QgsComposerItems on page
282  QList<QGraphicsItem *> itemList = items();
283  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
284  for ( ; itemIt != itemList.end(); ++itemIt )
285  {
286  const QgsComposerItem *composerItem = dynamic_cast<const QgsComposerItem *>( *itemIt );
287  const QgsPaperItem *paperItem = dynamic_cast<const QgsPaperItem *>( *itemIt );
288  if ( composerItem && !paperItem && itemPageNumber( composerItem ) == pageNumber )
289  {
290  if ( visibleOnly && !composerItem->isVisible() )
291  continue;
292 
293  //expand bounds with current item's bounds
294  if ( bounds.isValid() )
295  bounds = bounds.united( ( *itemIt )->sceneBoundingRect() );
296  else
297  bounds = ( *itemIt )->sceneBoundingRect();
298  }
299  }
300 
301  return bounds;
302 }
303 
304 void QgsComposition::setPaperSize( const double width, const double height, bool keepRelativeItemPosition )
305 {
306  if ( qgsDoubleNear( width, mPageWidth ) && qgsDoubleNear( height, mPageHeight ) )
307  {
308  return;
309  }
310 
311  if ( keepRelativeItemPosition )
312  {
313  //update item positions
314  QList<QGraphicsItem *> itemList = items();
315  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
316  for ( ; itemIt != itemList.end(); ++itemIt )
317  {
318  QgsComposerItem *composerItem = dynamic_cast<QgsComposerItem *>( *itemIt );
319  if ( composerItem )
320  {
321  composerItem->updatePagePos( width, height );
322  }
323  }
324  }
325 
326  //update guide positions and size
327  QList< QGraphicsLineItem * > *guides = snapLines();
328  QList< QGraphicsLineItem * >::iterator guideIt = guides->begin();
329  double totalHeight = ( height + spaceBetweenPages() ) * ( numPages() - 1 ) + height;
330  for ( ; guideIt != guides->end(); ++guideIt )
331  {
332  QLineF line = ( *guideIt )->line();
333  if ( qgsDoubleNear( line.dx(), 0. ) )
334  {
335  //vertical line, change height of line
336  ( *guideIt )->setLine( line.x1(), 0, line.x1(), totalHeight );
337  }
338  else
339  {
340  //horizontal line
341  if ( keepRelativeItemPosition )
342  {
343  //move to new vertical position and change width of line
344  QPointF curPagePos = positionOnPage( line.p1() );
345  int curPage = pageNumberForPoint( line.p1() ) - 1;
346  double newY = curPage * ( height + spaceBetweenPages() ) + curPagePos.y();
347  ( *guideIt )->setLine( 0, newY, width, newY );
348  }
349  else
350  {
351  //just resize guide to new page size
352  ( *guideIt )->setLine( 0, line.y1(), width, line.y1() );
353  }
354  }
355  }
356 
357  mPageWidth = width;
358  mPageHeight = height;
359  double currentY = 0;
360  for ( int i = 0; i < mPages.size(); ++i )
361  {
362  mPages.at( i )->setSceneRect( QRectF( 0, currentY, width, height ) );
363  currentY += ( height + mSpaceBetweenPages );
364  }
365  mProject->setDirty( true );
366  updateBounds();
367  emit paperSizeChanged();
368 }
369 
371 {
372  return mPageHeight;
373 }
374 
376 {
377  return mPageWidth;
378 }
379 
380 void QgsComposition::resizePageToContents( double marginTop, double marginRight, double marginBottom, double marginLeft )
381 {
382  //calculate current bounds
383  QRectF bounds = compositionBounds( true, 0.0 );
384 
385  setNumPages( 1 );
386  double newWidth = bounds.width() + marginLeft + marginRight;
387  double newHeight = bounds.height() + marginTop + marginBottom;
388  setPaperSize( newWidth, newHeight, false );
389 
390  //also move all items so that top-left of bounds is at marginLeft, marginTop
391  double diffX = marginLeft - bounds.left();
392  double diffY = marginTop - bounds.top();
393 
394  QList<QGraphicsItem *> itemList = items();
395  Q_FOREACH ( QGraphicsItem *item, itemList )
396  {
397  QgsComposerItem *composerItem = dynamic_cast<QgsComposerItem *>( item );
398  if ( composerItem )
399  {
400  const QgsPaperItem *paperItem = dynamic_cast<const QgsPaperItem *>( item );
401 
402  if ( !paperItem )
403  composerItem->move( diffX, diffY );
404  }
405  }
406 
407  //also move guides
408  Q_FOREACH ( QGraphicsLineItem *guide, mSnapLines )
409  {
410  QLineF line = guide->line();
411  if ( qgsDoubleNear( line.dx(), 0.0 ) )
412  {
413  //vertical line
414  guide->setLine( line.x1() + diffX, 0, line.x1() + diffX, newHeight );
415  }
416  else
417  {
418  //horizontal line
419  guide->setLine( 0, line.y1() + diffY, newWidth, line.y1() + diffY );
420  }
421  }
422 }
423 
424 void QgsComposition::setResizeToContentsMargins( double marginTop, double marginRight, double marginBottom, double marginLeft )
425 {
426  mResizeToContentsMarginTop = marginTop;
427  mResizeToContentsMarginRight = marginRight;
428  mResizeToContentsMarginBottom = marginBottom;
429  mResizeToContentsMarginLeft = marginLeft;
430 }
431 
432 void QgsComposition::resizeToContentsMargins( double &marginTop, double &marginRight, double &marginBottom, double &marginLeft ) const
433 {
434  marginTop = mResizeToContentsMarginTop;
435  marginRight = mResizeToContentsMarginRight;
436  marginBottom = mResizeToContentsMarginBottom;
437  marginLeft = mResizeToContentsMarginLeft;
438 }
439 
441 {
442  int currentPages = numPages();
443  int desiredPages = pages;
444 
445  //data defined num pages set?
447  desiredPages = mDataDefinedProperties.valueAsInt( QgsComposerObject::NumPages, context, desiredPages );
448 
449  int diff = desiredPages - currentPages;
450  if ( diff >= 0 )
451  {
452  for ( int i = 0; i < diff; ++i )
453  {
454  addPaperItem();
455  }
456  }
457  else
458  {
459  diff = -diff;
460  for ( int i = 0; i < diff; ++i )
461  {
462  delete mPages.last();
463  mPages.removeLast();
464  }
465  }
466 
467  //update vertical guide height
468  QList< QGraphicsLineItem * > *guides = snapLines();
469  QList< QGraphicsLineItem * >::iterator guideIt = guides->begin();
470  double totalHeight = ( mPageHeight + spaceBetweenPages() ) * ( pages - 1 ) + mPageHeight;
471  for ( ; guideIt != guides->end(); ++guideIt )
472  {
473  QLineF line = ( *guideIt )->line();
474  if ( qgsDoubleNear( line.dx(), 0.0 ) )
475  {
476  //vertical line, change height of line
477  ( *guideIt )->setLine( line.x1(), 0, line.x1(), totalHeight );
478  }
479  }
480 
481  mProject->setDirty( true );
482  updateBounds();
483 
484  emit nPagesChanged();
485 }
486 
488 {
489  return mPages.size();
490 }
491 
492 bool QgsComposition::pageIsEmpty( const int page ) const
493 {
494  //get all items on page
495  QList<QgsComposerItem *> items;
496  //composerItemsOnPage uses 0-based page numbering
497  composerItemsOnPage( items, page - 1 );
498 
499  //loop through and check for non-paper items
500  QList<QgsComposerItem *>::const_iterator itemIt = items.constBegin();
501  for ( ; itemIt != items.constEnd(); ++itemIt )
502  {
503  //is item a paper item?
504  QgsPaperItem *paper = dynamic_cast<QgsPaperItem *>( *itemIt );
505  if ( !paper )
506  {
507  //item is not a paper item, so we have other items on the page
508  return false;
509  }
510  }
511  //no non-paper items
512  return true;
513 }
514 
515 bool QgsComposition::shouldExportPage( const int page ) const
516 {
517  if ( page > numPages() || page < 1 )
518  {
519  //page number out of range
520  return false;
521  }
522 
523  //check all frame items on page
524  QList<QgsComposerFrame *> frames;
525  //composerItemsOnPage uses 0 based page numbering
526  composerItemsOnPage( frames, page - 1 );
527  QList<QgsComposerFrame *>::const_iterator frameIt = frames.constBegin();
528  for ( ; frameIt != frames.constEnd(); ++frameIt )
529  {
530  if ( ( *frameIt )->hidePageIfEmpty() && ( *frameIt )->isEmpty() )
531  {
532  //frame is set to hide page if empty, and frame is empty, so we don't want to export this page
533  return false;
534  }
535  }
536  return true;
537 }
538 
540 {
541  delete mPageStyleSymbol;
542  mPageStyleSymbol = static_cast<QgsFillSymbol *>( symbol->clone() );
543  mProject->setDirty( true );
544 }
545 
546 void QgsComposition::createDefaultPageStyleSymbol()
547 {
548  delete mPageStyleSymbol;
549  QgsStringMap properties;
550  properties.insert( QStringLiteral( "color" ), QStringLiteral( "white" ) );
551  properties.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) );
552  properties.insert( QStringLiteral( "style_border" ), QStringLiteral( "no" ) );
553  properties.insert( QStringLiteral( "joinstyle" ), QStringLiteral( "miter" ) );
554  mPageStyleSymbol = QgsFillSymbol::createSimple( properties );
555 }
556 
557 QPointF QgsComposition::positionOnPage( QPointF position ) const
558 {
559  double y;
560  if ( position.y() > ( mPages.size() - 1 ) * ( paperHeight() + spaceBetweenPages() ) )
561  {
562  //y coordinate is greater then the end of the last page, so return distance between
563  //top of last page and y coordinate
564  y = position.y() - ( mPages.size() - 1 ) * ( paperHeight() + spaceBetweenPages() );
565  }
566  else
567  {
568  //y coordinate is less then the end of the last page
569  y = std::fmod( position.y(), ( paperHeight() + spaceBetweenPages() ) );
570  }
571  return QPointF( position.x(), y );
572 }
573 
574 int QgsComposition::pageNumberForPoint( QPointF position ) const
575 {
576  int pageNumber = std::floor( position.y() / ( paperHeight() + spaceBetweenPages() ) ) + 1;
577  pageNumber = pageNumber < 1 ? 1 : pageNumber;
578  pageNumber = pageNumber > mPages.size() ? mPages.size() : pageNumber;
579  return pageNumber;
580 }
581 
582 void QgsComposition::setStatusMessage( const QString &message )
583 {
584  emit statusMsgChanged( message );
585 }
586 
587 QgsComposerItem *QgsComposition::composerItemAt( QPointF position, bool ignoreLocked ) const
588 {
589  return composerItemAt( position, nullptr, ignoreLocked );
590 }
591 
592 QgsComposerItem *QgsComposition::composerItemAt( QPointF position, const QgsComposerItem *belowItem, const bool ignoreLocked ) const
593 {
594  //get a list of items which intersect the specified position, in descending z order
595  QList<QGraphicsItem *> itemList;
596  itemList = items( position, Qt::IntersectsItemShape, Qt::DescendingOrder );
597  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
598 
599  bool foundBelowItem = false;
600  for ( ; itemIt != itemList.end(); ++itemIt )
601  {
602  QgsComposerItem *composerItem = dynamic_cast<QgsComposerItem *>( *itemIt );
603  QgsPaperItem *paperItem = dynamic_cast<QgsPaperItem *>( *itemIt );
604  if ( composerItem && !paperItem )
605  {
606  // If we are not checking for a an item below a specified item, or if we've
607  // already found that item, then we've found our target
608  if ( ( ! belowItem || foundBelowItem ) && ( !ignoreLocked || !composerItem->positionLock() ) )
609  {
610  return composerItem;
611  }
612  else
613  {
614  if ( composerItem == belowItem )
615  {
616  //Target item is next in list
617  foundBelowItem = true;
618  }
619  }
620  }
621  }
622  return nullptr;
623 }
624 
625 int QgsComposition::pageNumberAt( QPointF position ) const
626 {
627  return position.y() / ( paperHeight() + spaceBetweenPages() );
628 }
629 
631 {
632  return pageNumberAt( QPointF( item->pos().x(), item->pos().y() ) );
633 }
634 
635 QList<QgsComposerItem *> QgsComposition::selectedComposerItems( const bool includeLockedItems )
636 {
637  QList<QgsComposerItem *> composerItemList;
638 
639  QList<QGraphicsItem *> graphicsItemList = selectedItems();
640  QList<QGraphicsItem *>::iterator itemIter = graphicsItemList.begin();
641 
642  for ( ; itemIter != graphicsItemList.end(); ++itemIter )
643  {
644  QgsComposerItem *composerItem = dynamic_cast<QgsComposerItem *>( *itemIter );
645  if ( composerItem && ( includeLockedItems || !composerItem->positionLock() ) )
646  {
647  composerItemList.push_back( composerItem );
648  }
649  }
650 
651  return composerItemList;
652 }
653 
654 QList<const QgsComposerMap *> QgsComposition::composerMapItems() const
655 {
656  QList<const QgsComposerMap *> resultList;
657 
658  QList<QGraphicsItem *> itemList = items();
659  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
660  for ( ; itemIt != itemList.end(); ++itemIt )
661  {
662  const QgsComposerMap *composerMap = dynamic_cast<const QgsComposerMap *>( *itemIt );
663  if ( composerMap )
664  {
665  resultList.push_back( composerMap );
666  }
667  }
668 
669  return resultList;
670 }
671 
673 {
674  QList<QGraphicsItem *> itemList = items();
675  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
676  for ( ; itemIt != itemList.end(); ++itemIt )
677  {
678  const QgsComposerMap *composerMap = dynamic_cast<const QgsComposerMap *>( *itemIt );
679  if ( composerMap )
680  {
681  if ( composerMap->id() == id )
682  {
683  return composerMap;
684  }
685  }
686  }
687  return nullptr;
688 }
689 
690 const QgsComposerItem *QgsComposition::getComposerItemById( const QString &id ) const
691 {
692  QList<QGraphicsItem *> itemList = items();
693  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
694  for ( ; itemIt != itemList.end(); ++itemIt )
695  {
696  const QgsComposerItem *mypItem = dynamic_cast<const QgsComposerItem *>( *itemIt );
697  if ( mypItem )
698  {
699  if ( mypItem->id() == id )
700  {
701  return mypItem;
702  }
703  }
704  }
705  return nullptr;
706 }
707 
708 #if 0
709 const QgsComposerItem *QgsComposition::getComposerItemByUuid( QString uuid, bool inAllComposers ) const
710 {
711  //This does not work since it seems impossible to get the QgisApp::instance() from here... Is there a workaround ?
712  QSet<QgsComposer *> composers = QSet<QgsComposer *>();
713 
714  if ( inAllComposers )
715  {
716  composers = QgisApp::instance()->printComposers();
717  }
718  else
719  {
720  composers.insert( this )
721  }
722 
723  QSet<QgsComposer *>::const_iterator it = composers.constBegin();
724  for ( ; it != composers.constEnd(); ++it )
725  {
726  QList<QGraphicsItem *> itemList = ( *it )->items();
727  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
728  for ( ; itemIt != itemList.end(); ++itemIt )
729  {
730  const QgsComposerItem *mypItem = dynamic_cast<const QgsComposerItem *>( *itemIt );
731  if ( mypItem )
732  {
733  if ( mypItem->uuid() == uuid )
734  {
735  return mypItem;
736  }
737  }
738  }
739  }
740 
741  return 0;
742 }
743 #endif
744 
745 const QgsComposerItem *QgsComposition::getComposerItemByUuid( const QString &uuid ) const
746 {
747  QList<QGraphicsItem *> itemList = items();
748  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
749  for ( ; itemIt != itemList.end(); ++itemIt )
750  {
751  const QgsComposerItem *mypItem = dynamic_cast<const QgsComposerItem *>( *itemIt );
752  if ( mypItem )
753  {
754  if ( mypItem->uuid() == uuid )
755  {
756  return mypItem;
757  }
758  }
759  }
760 
761  return nullptr;
762 }
763 
765 {
766  mPrintResolution = dpi;
767  emit printResolutionChanged();
768  mProject->setDirty( true );
769 }
770 
772 {
773  // prefer explicitly set reference map
774  if ( QgsComposerMap *map = dynamic_cast< QgsComposerMap * >( const_cast< QgsComposerItem * >( getComposerItemByUuid( mWorldFileMapId ) ) ) )
775  return map;
776 
777  // else try to find largest map
778  QList< const QgsComposerMap * > maps = composerMapItems();
779  const QgsComposerMap *largestMap = nullptr;
780  double largestMapArea = 0;
781  Q_FOREACH ( const QgsComposerMap *map, maps )
782  {
783  double area = map->rect().width() * map->rect().height();
784  if ( area > largestMapArea )
785  {
786  largestMapArea = area;
787  largestMap = map;
788  }
789  }
790  return const_cast< QgsComposerMap * >( largestMap );
791 }
792 
794 {
795  mWorldFileMapId = map ? map->uuid() : QString();
796  mProject->setDirty( true );
797 }
798 
799 void QgsComposition::setUseAdvancedEffects( const bool effectsEnabled )
800 {
801  mUseAdvancedEffects = effectsEnabled;
802 
803  //toggle effects for all composer items
804  QList<QGraphicsItem *> itemList = items();
805  QList<QGraphicsItem *>::const_iterator itemIt = itemList.constBegin();
806  for ( ; itemIt != itemList.constEnd(); ++itemIt )
807  {
808  QgsComposerItem *composerItem = dynamic_cast<QgsComposerItem *>( *itemIt );
809  if ( composerItem )
810  {
811  composerItem->setEffectsEnabled( effectsEnabled );
812  }
813  }
814 }
815 
816 bool QgsComposition::writeXml( QDomElement &composerElem, QDomDocument &doc )
817 {
818  if ( composerElem.isNull() )
819  {
820  return false;
821  }
822 
823  QDomElement compositionElem = doc.createElement( QStringLiteral( "Composition" ) );
824  compositionElem.setAttribute( QStringLiteral( "name" ), mName );
825  compositionElem.setAttribute( QStringLiteral( "paperWidth" ), QString::number( mPageWidth ) );
826  compositionElem.setAttribute( QStringLiteral( "paperHeight" ), QString::number( mPageHeight ) );
827  compositionElem.setAttribute( QStringLiteral( "numPages" ), mPages.size() );
828 
829  QgsReadWriteContext context;
830  context.setPathResolver( mProject->pathResolver() );
831 
832  QDomElement pageStyleElem = QgsSymbolLayerUtils::saveSymbol( QString(), mPageStyleSymbol, doc, context );
833  compositionElem.appendChild( pageStyleElem );
834 
835  //snapping
836  if ( mSnapToGrid )
837  {
838  compositionElem.setAttribute( QStringLiteral( "snapping" ), QStringLiteral( "1" ) );
839  }
840  else
841  {
842  compositionElem.setAttribute( QStringLiteral( "snapping" ), QStringLiteral( "0" ) );
843  }
844  if ( mGridVisible )
845  {
846  compositionElem.setAttribute( QStringLiteral( "gridVisible" ), QStringLiteral( "1" ) );
847  }
848  else
849  {
850  compositionElem.setAttribute( QStringLiteral( "gridVisible" ), QStringLiteral( "0" ) );
851  }
852  compositionElem.setAttribute( QStringLiteral( "snapGridResolution" ), QString::number( mSnapGridResolution ) );
853  compositionElem.setAttribute( QStringLiteral( "snapGridOffsetX" ), QString::number( mSnapGridOffsetX ) );
854  compositionElem.setAttribute( QStringLiteral( "snapGridOffsetY" ), QString::number( mSnapGridOffsetY ) );
855 
856  compositionElem.setAttribute( QStringLiteral( "showPages" ), mPagesVisible );
857 
858  //custom snap lines
859  QList< QGraphicsLineItem * >::const_iterator snapLineIt = mSnapLines.constBegin();
860  for ( ; snapLineIt != mSnapLines.constEnd(); ++snapLineIt )
861  {
862  QDomElement snapLineElem = doc.createElement( QStringLiteral( "SnapLine" ) );
863  QLineF line = ( *snapLineIt )->line();
864  snapLineElem.setAttribute( QStringLiteral( "x1" ), QString::number( line.x1() ) );
865  snapLineElem.setAttribute( QStringLiteral( "y1" ), QString::number( line.y1() ) );
866  snapLineElem.setAttribute( QStringLiteral( "x2" ), QString::number( line.x2() ) );
867  snapLineElem.setAttribute( QStringLiteral( "y2" ), QString::number( line.y2() ) );
868  compositionElem.appendChild( snapLineElem );
869  }
870 
871  compositionElem.setAttribute( QStringLiteral( "printResolution" ), mPrintResolution );
872  compositionElem.setAttribute( QStringLiteral( "printAsRaster" ), mPrintAsRaster );
873 
874  compositionElem.setAttribute( QStringLiteral( "generateWorldFile" ), mGenerateWorldFile ? 1 : 0 );
875  compositionElem.setAttribute( QStringLiteral( "worldFileMap" ), mWorldFileMapId );
876 
877  compositionElem.setAttribute( QStringLiteral( "alignmentSnap" ), mAlignmentSnap ? 1 : 0 );
878  compositionElem.setAttribute( QStringLiteral( "guidesVisible" ), mGuidesVisible ? 1 : 0 );
879  compositionElem.setAttribute( QStringLiteral( "smartGuides" ), mSmartGuides ? 1 : 0 );
880  compositionElem.setAttribute( QStringLiteral( "snapTolerancePixels" ), mSnapTolerance );
881 
882  compositionElem.setAttribute( QStringLiteral( "resizeToContentsMarginTop" ), mResizeToContentsMarginTop );
883  compositionElem.setAttribute( QStringLiteral( "resizeToContentsMarginRight" ), mResizeToContentsMarginRight );
884  compositionElem.setAttribute( QStringLiteral( "resizeToContentsMarginBottom" ), mResizeToContentsMarginBottom );
885  compositionElem.setAttribute( QStringLiteral( "resizeToContentsMarginLeft" ), mResizeToContentsMarginLeft );
886 
887  //save items except paper items and frame items (they are saved with the corresponding multiframe)
888  QList<QGraphicsItem *> itemList = items();
889  QList<QGraphicsItem *>::const_iterator itemIt = itemList.constBegin();
890  for ( ; itemIt != itemList.constEnd(); ++itemIt )
891  {
892  const QgsComposerItem *composerItem = dynamic_cast<const QgsComposerItem *>( *itemIt );
893  if ( composerItem )
894  {
895  if ( composerItem->type() != QgsComposerItem::ComposerPaper && composerItem->type() != QgsComposerItem::ComposerFrame )
896  {
897  composerItem->writeXml( compositionElem, doc );
898  }
899  }
900  }
901 
902  //save multiframes
903  QSet<QgsComposerMultiFrame *>::const_iterator multiFrameIt = mMultiFrames.constBegin();
904  for ( ; multiFrameIt != mMultiFrames.constEnd(); ++multiFrameIt )
905  {
906  ( *multiFrameIt )->writeXml( compositionElem, doc );
907  }
908 
909  //data defined properties
910  QDomElement ddPropsElement = doc.createElement( QStringLiteral( "dataDefinedProperties" ) );
911  mDataDefinedProperties.writeXml( ddPropsElement, QgsComposerObject::propertyDefinitions() );
912  compositionElem.appendChild( ddPropsElement );
913 
914  composerElem.appendChild( compositionElem );
915 
916  //custom properties
917  mCustomProperties.writeXml( compositionElem, doc );
918 
919  return true;
920 }
921 
922 bool QgsComposition::readXml( const QDomElement &compositionElem, const QDomDocument &doc )
923 {
924  Q_UNUSED( doc );
925  if ( compositionElem.isNull() )
926  {
927  return false;
928  }
929 
930  setName( compositionElem.attribute( QStringLiteral( "name" ) ) );
931 
932  //create pages
933  bool widthConversionOk, heightConversionOk;
934  mPageWidth = compositionElem.attribute( QStringLiteral( "paperWidth" ) ).toDouble( &widthConversionOk );
935  mPageHeight = compositionElem.attribute( QStringLiteral( "paperHeight" ) ).toDouble( &heightConversionOk );
936  emit paperSizeChanged();
937  int numPages = compositionElem.attribute( QStringLiteral( "numPages" ), QStringLiteral( "1" ) ).toInt();
938 
939  QgsReadWriteContext context;
940  context.setPathResolver( mProject->pathResolver() );
941 
942  QDomElement pageStyleSymbolElem = compositionElem.firstChildElement( QStringLiteral( "symbol" ) );
943  if ( !pageStyleSymbolElem.isNull() )
944  {
945  delete mPageStyleSymbol;
946  mPageStyleSymbol = QgsSymbolLayerUtils::loadSymbol<QgsFillSymbol>( pageStyleSymbolElem, context );
947  }
948 
949  if ( widthConversionOk && heightConversionOk )
950  {
951  removePaperItems();
952  for ( int i = 0; i < numPages; ++i )
953  {
954  addPaperItem();
955  }
956  }
957 
958  //snapping
959  mSnapToGrid = compositionElem.attribute( QStringLiteral( "snapping" ), QStringLiteral( "0" ) ).toInt() != 0;
960  mGridVisible = compositionElem.attribute( QStringLiteral( "gridVisible" ), QStringLiteral( "0" ) ).toInt() != 0;
961 
962  mSnapGridResolution = compositionElem.attribute( QStringLiteral( "snapGridResolution" ) ).toDouble();
963  mSnapGridOffsetX = compositionElem.attribute( QStringLiteral( "snapGridOffsetX" ) ).toDouble();
964  mSnapGridOffsetY = compositionElem.attribute( QStringLiteral( "snapGridOffsetY" ) ).toDouble();
965 
966  mAlignmentSnap = compositionElem.attribute( QStringLiteral( "alignmentSnap" ), QStringLiteral( "1" ) ).toInt() != 0;
967  mGuidesVisible = compositionElem.attribute( QStringLiteral( "guidesVisible" ), QStringLiteral( "1" ) ).toInt() != 0;
968  mSmartGuides = compositionElem.attribute( QStringLiteral( "smartGuides" ), QStringLiteral( "1" ) ).toInt() != 0;
969  mSnapTolerance = compositionElem.attribute( QStringLiteral( "snapTolerancePixels" ), QStringLiteral( "10" ) ).toInt();
970 
971  mResizeToContentsMarginTop = compositionElem.attribute( QStringLiteral( "resizeToContentsMarginTop" ), QStringLiteral( "0" ) ).toDouble();
972  mResizeToContentsMarginRight = compositionElem.attribute( QStringLiteral( "resizeToContentsMarginRight" ), QStringLiteral( "0" ) ).toDouble();
973  mResizeToContentsMarginBottom = compositionElem.attribute( QStringLiteral( "resizeToContentsMarginBottom" ), QStringLiteral( "0" ) ).toDouble();
974  mResizeToContentsMarginLeft = compositionElem.attribute( QStringLiteral( "resizeToContentsMarginLeft" ), QStringLiteral( "0" ) ).toDouble();
975 
976  //custom snap lines
977  QDomNodeList snapLineNodes = compositionElem.elementsByTagName( QStringLiteral( "SnapLine" ) );
978  for ( int i = 0; i < snapLineNodes.size(); ++i )
979  {
980  QDomElement snapLineElem = snapLineNodes.at( i ).toElement();
981  QGraphicsLineItem *snapItem = addSnapLine();
982  double x1 = snapLineElem.attribute( QStringLiteral( "x1" ) ).toDouble();
983  double y1 = snapLineElem.attribute( QStringLiteral( "y1" ) ).toDouble();
984  double x2 = snapLineElem.attribute( QStringLiteral( "x2" ) ).toDouble();
985  double y2 = snapLineElem.attribute( QStringLiteral( "y2" ) ).toDouble();
986  snapItem->setLine( x1, y1, x2, y2 );
987  }
988 
989  mPagesVisible = ( compositionElem.attribute( QStringLiteral( "showPages" ), QStringLiteral( "1" ) ) != QLatin1String( "0" ) );
990  mPrintAsRaster = compositionElem.attribute( QStringLiteral( "printAsRaster" ) ).toInt();
991  mPrintResolution = compositionElem.attribute( QStringLiteral( "printResolution" ), QStringLiteral( "300" ) ).toInt();
992 
993  mGenerateWorldFile = compositionElem.attribute( QStringLiteral( "generateWorldFile" ), QStringLiteral( "0" ) ).toInt() == 1;
994  mWorldFileMapId = compositionElem.attribute( QStringLiteral( "worldFileMap" ) );
995 
996  //data defined properties
997  //read old (pre 3.0) style data defined properties
998  QgsComposerUtils::readOldDataDefinedPropertyMap( compositionElem, mDataDefinedProperties );
999 
1000  QDomNode propsNode = compositionElem.namedItem( QStringLiteral( "dataDefinedProperties" ) );
1001  if ( !propsNode.isNull() )
1002  {
1003  mDataDefinedProperties.readXml( propsNode.toElement(), QgsComposerObject::propertyDefinitions() );
1004  }
1005 
1006  //custom properties
1007  mCustomProperties.readXml( compositionElem );
1008 
1009  updatePaperItems();
1010 
1011  updateBounds();
1012 
1013  emit variablesChanged();
1014 
1015  return true;
1016 }
1017 
1018 bool QgsComposition::loadFromTemplate( const QDomDocument &doc, QMap<QString, QString> *substitutionMap, bool addUndoCommands, const bool clearComposition )
1019 {
1020  if ( clearComposition )
1021  {
1022  deleteAndRemoveMultiFrames();
1023 
1024  //delete all non paper items and emit itemRemoved signal
1025  QList<QGraphicsItem *> itemList = items();
1026  QList<QGraphicsItem *>::iterator itemIter = itemList.begin();
1027  for ( ; itemIter != itemList.end(); ++itemIter )
1028  {
1029  QgsComposerItem *cItem = dynamic_cast<QgsComposerItem *>( *itemIter );
1030  QgsPaperItem *pItem = dynamic_cast<QgsPaperItem *>( *itemIter );
1031  if ( cItem && !pItem )
1032  {
1033  removeItem( cItem );
1034  emit itemRemoved( cItem );
1035  delete cItem;
1036  }
1037  }
1038  mItemsModel->clear();
1039 
1040  removePaperItems();
1041  mUndoStack->clear();
1042  }
1043 
1044  QDomDocument importDoc;
1045  if ( substitutionMap )
1046  {
1047  QString xmlString = doc.toString();
1048  QMap<QString, QString>::const_iterator sIt = substitutionMap->constBegin();
1049  for ( ; sIt != substitutionMap->constEnd(); ++sIt )
1050  {
1051  xmlString = xmlString.replace( '[' + sIt.key() + ']', encodeStringForXml( sIt.value() ) );
1052  }
1053 
1054  QString errorMsg;
1055  int errorLine, errorColumn;
1056  if ( !importDoc.setContent( xmlString, &errorMsg, &errorLine, &errorColumn ) )
1057  {
1058  return false;
1059  }
1060  }
1061  else
1062  {
1063  importDoc = doc;
1064  }
1065 
1066  //read general settings
1067  QDomElement atlasElem;
1068  if ( clearComposition )
1069  {
1070  QDomElement compositionElem = importDoc.documentElement().firstChildElement( QStringLiteral( "Composition" ) );
1071  if ( compositionElem.isNull() )
1072  {
1073  return false;
1074  }
1075 
1076  bool ok = readXml( compositionElem, importDoc );
1077  if ( !ok )
1078  {
1079  return false;
1080  }
1081 
1082  // read atlas parameters - must be done before adding items
1083  atlasElem = importDoc.documentElement().firstChildElement( QStringLiteral( "Atlas" ) );
1084  atlasComposition().readXml( atlasElem, importDoc );
1085  }
1086 
1087  // remove all uuid attributes since we don't want duplicates UUIDS
1088  QDomNodeList composerItemsNodes = importDoc.elementsByTagName( QStringLiteral( "ComposerItem" ) );
1089  for ( int i = 0; i < composerItemsNodes.count(); ++i )
1090  {
1091  QDomNode composerItemNode = composerItemsNodes.at( i );
1092  if ( composerItemNode.isElement() )
1093  {
1094  composerItemNode.toElement().setAttribute( QStringLiteral( "templateUuid" ), composerItemNode.toElement().attribute( QStringLiteral( "uuid" ) ) );
1095  composerItemNode.toElement().removeAttribute( QStringLiteral( "uuid" ) );
1096  }
1097  }
1098 
1099  //addItemsFromXML
1100  addItemsFromXml( importDoc.documentElement(), importDoc, addUndoCommands, nullptr );
1101 
1102  return true;
1103 }
1104 
1105 QPointF QgsComposition::minPointFromXml( const QDomElement &elem ) const
1106 {
1107  double minX = std::numeric_limits<double>::max();
1108  double minY = std::numeric_limits<double>::max();
1109  QDomNodeList composerItemList = elem.elementsByTagName( QStringLiteral( "ComposerItem" ) );
1110  for ( int i = 0; i < composerItemList.size(); ++i )
1111  {
1112  QDomElement currentComposerItemElem = composerItemList.at( i ).toElement();
1113  double x, y;
1114  bool xOk, yOk;
1115  x = currentComposerItemElem.attribute( QStringLiteral( "x" ) ).toDouble( &xOk );
1116  y = currentComposerItemElem.attribute( QStringLiteral( "y" ) ).toDouble( &yOk );
1117  if ( !xOk || !yOk )
1118  {
1119  continue;
1120  }
1121  minX = std::min( minX, x );
1122  minY = std::min( minY, y );
1123  }
1124  if ( minX < std::numeric_limits<double>::max() )
1125  {
1126  return QPointF( minX, minY );
1127  }
1128  else
1129  {
1130  return QPointF( 0, 0 );
1131  }
1132 }
1133 
1134 void QgsComposition::addItemsFromXml( const QDomElement &elem, const QDomDocument &doc,
1135  bool addUndoCommands, QPointF *pos, bool pasteInPlace )
1136 {
1137  QPointF *pasteInPlacePt = nullptr;
1138 
1139  //if we are adding items to a composition which already contains items, we need to make sure
1140  //these items are placed at the top of the composition and that zValues are not duplicated
1141  //so, calculate an offset which needs to be added to the zValue of created items
1142  int zOrderOffset = mItemsModel->zOrderListSize();
1143 
1144  QPointF pasteShiftPos;
1145  QgsComposerItem *lastPastedItem = nullptr;
1146  if ( pos )
1147  {
1148  //If we are placing items relative to a certain point, then calculate how much we need
1149  //to shift the items by so that they are placed at this point
1150  //First, calculate the minimum position from the xml
1151  QPointF minItemPos = minPointFromXml( elem );
1152  //next, calculate how much each item needs to be shifted from its original position
1153  //so that it's placed at the correct relative position
1154  pasteShiftPos = *pos - minItemPos;
1155 
1156  //since we are pasting items, clear the existing selection
1157  setAllDeselected();
1158 
1159  if ( pasteInPlace )
1160  {
1161  pasteInPlacePt = new QPointF( 0, pageNumberAt( *pos ) * ( mPageHeight + mSpaceBetweenPages ) );
1162  }
1163  }
1164  QDomNodeList composerLabelList = elem.elementsByTagName( QStringLiteral( "ComposerLabel" ) );
1165  for ( int i = 0; i < composerLabelList.size(); ++i )
1166  {
1167  QDomElement currentComposerLabelElem = composerLabelList.at( i ).toElement();
1168  QgsComposerLabel *newLabel = new QgsComposerLabel( this );
1169  newLabel->readXml( currentComposerLabelElem, doc );
1170  if ( pos )
1171  {
1172  if ( pasteInPlacePt )
1173  {
1174  newLabel->setItemPosition( newLabel->pos().x(), std::fmod( newLabel->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
1175  newLabel->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
1176  }
1177  else
1178  {
1179  newLabel->move( pasteShiftPos.x(), pasteShiftPos.y() );
1180  }
1181  newLabel->setSelected( true );
1182  lastPastedItem = newLabel;
1183  }
1184  addComposerLabel( newLabel );
1185  newLabel->setZValue( newLabel->zValue() + zOrderOffset );
1186  if ( addUndoCommands )
1187  {
1188  pushAddRemoveCommand( newLabel, tr( "Label added" ) );
1189  }
1190  }
1191  // map
1192  QDomNodeList composerMapList = elem.elementsByTagName( QStringLiteral( "ComposerMap" ) );
1193  for ( int i = 0; i < composerMapList.size(); ++i )
1194  {
1195  QDomElement currentComposerMapElem = composerMapList.at( i ).toElement();
1196  QgsComposerMap *newMap = new QgsComposerMap( this );
1197 
1198  newMap->readXml( currentComposerMapElem, doc );
1199  newMap->assignFreeId();
1200 
1201  addComposerMap( newMap );
1202  newMap->setZValue( newMap->zValue() + zOrderOffset );
1203  if ( pos )
1204  {
1205  if ( pasteInPlace )
1206  {
1207  newMap->setItemPosition( newMap->pos().x(), std::fmod( newMap->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
1208  newMap->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
1209  }
1210  else
1211  {
1212  newMap->move( pasteShiftPos.x(), pasteShiftPos.y() );
1213  }
1214  newMap->setSelected( true );
1215  lastPastedItem = newMap;
1216  }
1217 
1218  if ( addUndoCommands )
1219  {
1220  pushAddRemoveCommand( newMap, tr( "Map added" ) );
1221  }
1222  }
1223  //now that all map items have been created, re-connect overview map signals
1224  QList<QgsComposerMap *> maps;
1225  composerItems( maps );
1226  for ( QList<QgsComposerMap *>::iterator mit = maps.begin(); mit != maps.end(); ++mit )
1227  {
1228  QgsComposerMap *map = ( *mit );
1229  if ( map )
1230  {
1231  QList<QgsComposerMapOverview * > overviews = map->overviews()->asList();
1232  QList<QgsComposerMapOverview * >::iterator overviewIt = overviews.begin();
1233  for ( ; overviewIt != overviews.end(); ++overviewIt )
1234  {
1235  ( *overviewIt )->connectSignals();
1236  }
1237  }
1238  }
1239 
1240  // arrow
1241  QDomNodeList composerArrowList = elem.elementsByTagName( QStringLiteral( "ComposerArrow" ) );
1242  for ( int i = 0; i < composerArrowList.size(); ++i )
1243  {
1244  QDomElement currentComposerArrowElem = composerArrowList.at( i ).toElement();
1245  QgsComposerArrow *newArrow = new QgsComposerArrow( this );
1246  newArrow->readXml( currentComposerArrowElem, doc );
1247  if ( pos )
1248  {
1249  if ( pasteInPlace )
1250  {
1251  newArrow->setItemPosition( newArrow->pos().x(), std::fmod( newArrow->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
1252  newArrow->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
1253  }
1254  else
1255  {
1256  newArrow->move( pasteShiftPos.x(), pasteShiftPos.y() );
1257  }
1258  newArrow->setSelected( true );
1259  lastPastedItem = newArrow;
1260  }
1261  addComposerArrow( newArrow );
1262  newArrow->setZValue( newArrow->zValue() + zOrderOffset );
1263  if ( addUndoCommands )
1264  {
1265  pushAddRemoveCommand( newArrow, tr( "Arrow added" ) );
1266  }
1267  }
1268  // scalebar
1269  QDomNodeList composerScaleBarList = elem.elementsByTagName( QStringLiteral( "ComposerScaleBar" ) );
1270  for ( int i = 0; i < composerScaleBarList.size(); ++i )
1271  {
1272  QDomElement currentComposerScaleBarElem = composerScaleBarList.at( i ).toElement();
1273  QgsComposerScaleBar *newScaleBar = new QgsComposerScaleBar( this );
1274  newScaleBar->readXml( currentComposerScaleBarElem, doc );
1275  if ( pos )
1276  {
1277  if ( pasteInPlace )
1278  {
1279  newScaleBar->setItemPosition( newScaleBar->pos().x(), std::fmod( newScaleBar->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
1280  newScaleBar->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
1281  }
1282  else
1283  {
1284  newScaleBar->move( pasteShiftPos.x(), pasteShiftPos.y() );
1285  }
1286  newScaleBar->setSelected( true );
1287  lastPastedItem = newScaleBar;
1288  }
1289  addComposerScaleBar( newScaleBar );
1290  newScaleBar->setZValue( newScaleBar->zValue() + zOrderOffset );
1291  if ( addUndoCommands )
1292  {
1293  pushAddRemoveCommand( newScaleBar, tr( "Scale bar added" ) );
1294  }
1295  }
1296  // shape
1297  QDomNodeList composerShapeList = elem.elementsByTagName( QStringLiteral( "ComposerShape" ) );
1298  for ( int i = 0; i < composerShapeList.size(); ++i )
1299  {
1300  QDomElement currentComposerShapeElem = composerShapeList.at( i ).toElement();
1301  QgsComposerShape *newShape = new QgsComposerShape( this );
1302  newShape->readXml( currentComposerShapeElem, doc );
1303  //new shapes should default to symbol v2
1304  newShape->setUseSymbol( true );
1305  if ( pos )
1306  {
1307  if ( pasteInPlace )
1308  {
1309  newShape->setItemPosition( newShape->pos().x(), std::fmod( newShape->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
1310  newShape->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
1311  }
1312  else
1313  {
1314  newShape->move( pasteShiftPos.x(), pasteShiftPos.y() );
1315  }
1316  newShape->setSelected( true );
1317  lastPastedItem = newShape;
1318  }
1319  addComposerShape( newShape );
1320  newShape->setZValue( newShape->zValue() + zOrderOffset );
1321  if ( addUndoCommands )
1322  {
1323  pushAddRemoveCommand( newShape, tr( "Shape added" ) );
1324  }
1325  }
1326 
1327  // polygon
1328  QDomNodeList composerPolygonList = elem.elementsByTagName( QStringLiteral( "ComposerPolygon" ) );
1329  for ( int i = 0; i < composerPolygonList.size(); ++i )
1330  {
1331  QDomElement currentComposerPolygonElem = composerPolygonList.at( i ).toElement();
1332  QgsComposerPolygon *newPolygon = new QgsComposerPolygon( this );
1333  newPolygon->readXml( currentComposerPolygonElem, doc );
1334 
1335  if ( pos )
1336  {
1337  if ( pasteInPlace )
1338  {
1339  newPolygon->setItemPosition( newPolygon->pos().x(), std::fmod( newPolygon->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
1340  newPolygon->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
1341  }
1342  else
1343  {
1344  newPolygon->move( pasteShiftPos.x(), pasteShiftPos.y() );
1345  }
1346  newPolygon->setSelected( true );
1347  lastPastedItem = newPolygon;
1348  }
1349 
1350  addComposerPolygon( newPolygon );
1351  newPolygon->setZValue( newPolygon->zValue() + zOrderOffset );
1352  if ( addUndoCommands )
1353  {
1354  pushAddRemoveCommand( newPolygon, tr( "Polygon added" ) );
1355  }
1356  }
1357 
1358  // polyline
1359  QDomNodeList addComposerPolylineList = elem.elementsByTagName( QStringLiteral( "ComposerPolyline" ) );
1360  for ( int i = 0; i < addComposerPolylineList.size(); ++i )
1361  {
1362  QDomElement currentComposerPolylineElem = addComposerPolylineList.at( i ).toElement();
1363  QgsComposerPolyline *newPolyline = new QgsComposerPolyline( this );
1364  newPolyline->readXml( currentComposerPolylineElem, doc );
1365 
1366  if ( pos )
1367  {
1368  if ( pasteInPlace )
1369  {
1370  newPolyline->setItemPosition( newPolyline->pos().x(), std::fmod( newPolyline->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
1371  newPolyline->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
1372  }
1373  else
1374  {
1375  newPolyline->move( pasteShiftPos.x(), pasteShiftPos.y() );
1376  }
1377  newPolyline->setSelected( true );
1378  lastPastedItem = newPolyline;
1379  }
1380 
1381  addComposerPolyline( newPolyline );
1382  newPolyline->setZValue( newPolyline->zValue() + zOrderOffset );
1383  if ( addUndoCommands )
1384  {
1385  pushAddRemoveCommand( newPolyline, tr( "Polyline added" ) );
1386  }
1387  }
1388 
1389  // picture
1390  QDomNodeList composerPictureList = elem.elementsByTagName( QStringLiteral( "ComposerPicture" ) );
1391  for ( int i = 0; i < composerPictureList.size(); ++i )
1392  {
1393  QDomElement currentComposerPictureElem = composerPictureList.at( i ).toElement();
1394  QgsComposerPicture *newPicture = new QgsComposerPicture( this );
1395  newPicture->readXml( currentComposerPictureElem, doc );
1396  if ( pos )
1397  {
1398  if ( pasteInPlace )
1399  {
1400  newPicture->setItemPosition( newPicture->pos().x(), std::fmod( newPicture->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
1401  newPicture->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
1402  }
1403  else
1404  {
1405  newPicture->move( pasteShiftPos.x(), pasteShiftPos.y() );
1406  }
1407  newPicture->setSelected( true );
1408  lastPastedItem = newPicture;
1409  }
1410  addComposerPicture( newPicture );
1411  newPicture->setZValue( newPicture->zValue() + zOrderOffset );
1412  if ( addUndoCommands )
1413  {
1414  pushAddRemoveCommand( newPicture, tr( "Picture added" ) );
1415  }
1416  }
1417  // legend
1418  QDomNodeList composerLegendList = elem.elementsByTagName( QStringLiteral( "ComposerLegend" ) );
1419  for ( int i = 0; i < composerLegendList.size(); ++i )
1420  {
1421  QDomElement currentComposerLegendElem = composerLegendList.at( i ).toElement();
1422  QgsComposerLegend *newLegend = new QgsComposerLegend( this );
1423  newLegend->readXml( currentComposerLegendElem, doc );
1424  if ( pos )
1425  {
1426  if ( pasteInPlace )
1427  {
1428  newLegend->setItemPosition( newLegend->pos().x(), std::fmod( newLegend->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
1429  newLegend->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
1430  }
1431  else
1432  {
1433  newLegend->move( pasteShiftPos.x(), pasteShiftPos.y() );
1434  }
1435  newLegend->setSelected( true );
1436  lastPastedItem = newLegend;
1437  }
1438  addComposerLegend( newLegend );
1439  newLegend->setZValue( newLegend->zValue() + zOrderOffset );
1440  if ( addUndoCommands )
1441  {
1442  pushAddRemoveCommand( newLegend, tr( "Legend added" ) );
1443  }
1444  }
1445 
1446  // html
1447  //TODO - fix this. pasting multiframe frame items has no effect
1448  QDomNodeList composerHtmlList = elem.elementsByTagName( QStringLiteral( "ComposerHtml" ) );
1449  for ( int i = 0; i < composerHtmlList.size(); ++i )
1450  {
1451  QDomElement currentHtmlElem = composerHtmlList.at( i ).toElement();
1452  QgsComposerHtml *newHtml = new QgsComposerHtml( this, false );
1453  newHtml->readXml( currentHtmlElem, doc );
1454  newHtml->setCreateUndoCommands( true );
1455  this->addMultiFrame( newHtml );
1456 
1457  //offset z values for frames
1458  //TODO - fix this after fixing html item paste
1459  /*for ( int frameIdx = 0; frameIdx < newHtml->frameCount(); ++frameIdx )
1460  {
1461  QgsComposerFrame * frame = newHtml->frame( frameIdx );
1462  frame->setZValue( frame->zValue() + zOrderOffset );
1463  }*/
1464  }
1465  QDomNodeList composerAttributeTableV2List = elem.elementsByTagName( QStringLiteral( "ComposerAttributeTableV2" ) );
1466  for ( int i = 0; i < composerAttributeTableV2List.size(); ++i )
1467  {
1468  QDomElement currentTableElem = composerAttributeTableV2List.at( i ).toElement();
1469  QgsComposerAttributeTableV2 *newTable = new QgsComposerAttributeTableV2( this, false );
1470  newTable->readXml( currentTableElem, doc );
1471  newTable->setCreateUndoCommands( true );
1472  this->addMultiFrame( newTable );
1473 
1474  //offset z values for frames
1475  //TODO - fix this after fixing html item paste
1476  /*for ( int frameIdx = 0; frameIdx < newHtml->frameCount(); ++frameIdx )
1477  {
1478  QgsComposerFrame * frame = newHtml->frame( frameIdx );
1479  frame->setZValue( frame->zValue() + zOrderOffset );
1480  }*/
1481  }
1482 
1483  // groups (must be last as it references uuids of above items)
1484  //TODO - pasted groups lose group properties, since the uuids of group items
1485  //changes
1486  QDomNodeList groupList = elem.elementsByTagName( QStringLiteral( "ComposerItemGroup" ) );
1487  for ( int i = 0; i < groupList.size(); ++i )
1488  {
1489  QDomElement groupElem = groupList.at( i ).toElement();
1490  QgsComposerItemGroup *newGroup = new QgsComposerItemGroup( this );
1491  newGroup->readXml( groupElem, doc );
1492  addItem( newGroup );
1493  if ( addUndoCommands )
1494  {
1495  pushAddRemoveCommand( newGroup, tr( "Group added" ) );
1496  }
1497  }
1498 
1499  //Since this function adds items grouped by type, and each item is added to end of
1500  //z order list in turn, it will now be inconsistent with the actual order of items in the scene.
1501  //Make sure z order list matches the actual order of items in the scene.
1502  mItemsModel->rebuildZList();
1503 
1504  if ( lastPastedItem )
1505  {
1506  emit selectedItemChanged( lastPastedItem );
1507  }
1508 
1509  delete pasteInPlacePt;
1510  pasteInPlacePt = nullptr;
1511 
1512 }
1513 
1515 {
1516  if ( !item )
1517  {
1518  return;
1519  }
1520 
1521  //model handles changes to z list
1522  mItemsModel->addItemAtTop( item );
1523 }
1524 
1526 {
1527  if ( !item )
1528  {
1529  return;
1530  }
1531 
1532  //model handles changes to z list
1533  mItemsModel->removeItem( item );
1534 }
1535 
1537 {
1538  QList<QgsComposerItem *> selectedItems = selectedComposerItems();
1539  QList<QgsComposerItem *>::iterator it = selectedItems.begin();
1540  bool itemsRaised = false;
1541  for ( ; it != selectedItems.end(); ++it )
1542  {
1543  itemsRaised = itemsRaised | raiseItem( *it );
1544  }
1545 
1546  if ( !itemsRaised )
1547  {
1548  //no change
1549  return;
1550  }
1551 
1552  //update all positions
1553  updateZValues();
1554  update();
1555 }
1556 
1558 {
1559  //model handles reordering items
1560  return mItemsModel->reorderItemUp( item );
1561 }
1562 
1564 {
1565  return mItemsModel->getComposerItemAbove( item );
1566 }
1567 
1569 {
1570  return mItemsModel->getComposerItemBelow( item );
1571 }
1572 
1574 {
1575  QgsComposerItem *previousSelectedItem = nullptr;
1576  QList<QgsComposerItem *> selectedItems = selectedComposerItems();
1577  if ( !selectedItems.isEmpty() )
1578  {
1579  previousSelectedItem = selectedItems.at( 0 );
1580  }
1581 
1582  if ( !previousSelectedItem )
1583  {
1584  return;
1585  }
1586 
1587  //select item with target z value
1588  QgsComposerItem *selectedItem = nullptr;
1589  switch ( direction )
1590  {
1592  selectedItem = getComposerItemBelow( previousSelectedItem );
1593  break;
1595  selectedItem = getComposerItemAbove( previousSelectedItem );
1596  break;
1597  }
1598 
1599  if ( !selectedItem )
1600  {
1601  return;
1602  }
1603 
1604  //OK, found a good target item
1605  setAllDeselected();
1606  selectedItem->setSelected( true );
1607  emit selectedItemChanged( selectedItem );
1608 }
1609 
1611 {
1612  QList<QgsComposerItem *> selectedItems = selectedComposerItems();
1613  QList<QgsComposerItem *>::iterator it = selectedItems.begin();
1614  bool itemsLowered = false;
1615  for ( ; it != selectedItems.end(); ++it )
1616  {
1617  itemsLowered = itemsLowered | lowerItem( *it );
1618  }
1619 
1620  if ( !itemsLowered )
1621  {
1622  //no change
1623  return;
1624  }
1625 
1626  //update all positions
1627  updateZValues();
1628  update();
1629 }
1630 
1632 {
1633  //model handles reordering items
1634  return mItemsModel->reorderItemDown( item );
1635 }
1636 
1638 {
1639  QList<QgsComposerItem *> selectedItems = selectedComposerItems();
1640  QList<QgsComposerItem *>::iterator it = selectedItems.begin();
1641  bool itemsRaised = false;
1642  for ( ; it != selectedItems.end(); ++it )
1643  {
1644  itemsRaised = itemsRaised | moveItemToTop( *it );
1645  }
1646 
1647  if ( !itemsRaised )
1648  {
1649  //no change
1650  return;
1651  }
1652 
1653  //update all positions
1654  updateZValues();
1655  update();
1656 }
1657 
1659 {
1660  //model handles reordering items
1661  return mItemsModel->reorderItemToTop( item );
1662 }
1663 
1665 {
1666  QList<QgsComposerItem *> selectedItems = selectedComposerItems();
1667  QList<QgsComposerItem *>::iterator it = selectedItems.begin();
1668  bool itemsLowered = false;
1669  for ( ; it != selectedItems.end(); ++it )
1670  {
1671  itemsLowered = itemsLowered | moveItemToBottom( *it );
1672  }
1673 
1674  if ( !itemsLowered )
1675  {
1676  //no change
1677  return;
1678  }
1679 
1680  //update all positions
1681  updateZValues();
1682  update();
1683 }
1684 
1686 {
1687  //model handles reordering items
1688  return mItemsModel->reorderItemToBottom( item );
1689 }
1690 
1692 {
1693  QList<QgsComposerItem *> selectedItems = selectedComposerItems();
1694  if ( selectedItems.size() < 2 )
1695  {
1696  return;
1697  }
1698 
1699  QRectF selectedItemBBox;
1700  if ( boundingRectOfSelectedItems( selectedItemBBox ) != 0 )
1701  {
1702  return;
1703  }
1704 
1705  double minXCoordinate = selectedItemBBox.left();
1706 
1707  //align items left to minimum x coordinate
1708  QUndoCommand *parentCommand = new QUndoCommand( tr( "Aligned items left" ) );
1709  QList<QgsComposerItem *>::iterator align_it = selectedItems.begin();
1710  for ( ; align_it != selectedItems.end(); ++align_it )
1711  {
1712  QgsComposerItemCommand *subcommand = new QgsComposerItemCommand( *align_it, QLatin1String( "" ), parentCommand );
1713  subcommand->savePreviousState();
1714  ( *align_it )->setPos( minXCoordinate, ( *align_it )->pos().y() );
1715  subcommand->saveAfterState();
1716  }
1717  mUndoStack->push( parentCommand );
1718  mProject->setDirty( true );
1719 }
1720 
1722 {
1723  QList<QgsComposerItem *> selectedItems = selectedComposerItems();
1724  if ( selectedItems.size() < 2 )
1725  {
1726  return;
1727  }
1728 
1729  QRectF selectedItemBBox;
1730  if ( boundingRectOfSelectedItems( selectedItemBBox ) != 0 )
1731  {
1732  return;
1733  }
1734 
1735  double averageXCoord = ( selectedItemBBox.left() + selectedItemBBox.right() ) / 2.0;
1736 
1737  //place items
1738  QUndoCommand *parentCommand = new QUndoCommand( tr( "Aligned items horizontal center" ) );
1739  QList<QgsComposerItem *>::iterator align_it = selectedItems.begin();
1740  for ( ; align_it != selectedItems.end(); ++align_it )
1741  {
1742  QgsComposerItemCommand *subcommand = new QgsComposerItemCommand( *align_it, QLatin1String( "" ), parentCommand );
1743  subcommand->savePreviousState();
1744  ( *align_it )->setPos( averageXCoord - ( *align_it )->rect().width() / 2.0, ( *align_it )->pos().y() );
1745  subcommand->saveAfterState();
1746  }
1747  mUndoStack->push( parentCommand );
1748  mProject->setDirty( true );
1749 }
1750 
1752 {
1753  QList<QgsComposerItem *> selectedItems = selectedComposerItems();
1754  if ( selectedItems.size() < 2 )
1755  {
1756  return;
1757  }
1758 
1759  QRectF selectedItemBBox;
1760  if ( boundingRectOfSelectedItems( selectedItemBBox ) != 0 )
1761  {
1762  return;
1763  }
1764 
1765  double maxXCoordinate = selectedItemBBox.right();
1766 
1767  //align items right to maximum x coordinate
1768  QUndoCommand *parentCommand = new QUndoCommand( tr( "Aligned items right" ) );
1769  QList<QgsComposerItem *>::iterator align_it = selectedItems.begin();
1770  for ( ; align_it != selectedItems.end(); ++align_it )
1771  {
1772  QgsComposerItemCommand *subcommand = new QgsComposerItemCommand( *align_it, QLatin1String( "" ), parentCommand );
1773  subcommand->savePreviousState();
1774  ( *align_it )->setPos( maxXCoordinate - ( *align_it )->rect().width(), ( *align_it )->pos().y() );
1775  subcommand->saveAfterState();
1776  }
1777  mUndoStack->push( parentCommand );
1778  mProject->setDirty( true );
1779 }
1780 
1782 {
1783  QList<QgsComposerItem *> selectedItems = selectedComposerItems();
1784  if ( selectedItems.size() < 2 )
1785  {
1786  return;
1787  }
1788 
1789  QRectF selectedItemBBox;
1790  if ( boundingRectOfSelectedItems( selectedItemBBox ) != 0 )
1791  {
1792  return;
1793  }
1794 
1795  double minYCoordinate = selectedItemBBox.top();
1796 
1797  QUndoCommand *parentCommand = new QUndoCommand( tr( "Aligned items top" ) );
1798  QList<QgsComposerItem *>::iterator align_it = selectedItems.begin();
1799  for ( ; align_it != selectedItems.end(); ++align_it )
1800  {
1801  QgsComposerItemCommand *subcommand = new QgsComposerItemCommand( *align_it, QLatin1String( "" ), parentCommand );
1802  subcommand->savePreviousState();
1803  ( *align_it )->setPos( ( *align_it )->pos().x(), minYCoordinate );
1804  subcommand->saveAfterState();
1805  }
1806  mUndoStack->push( parentCommand );
1807  mProject->setDirty( true );
1808 }
1809 
1811 {
1812  QList<QgsComposerItem *> selectedItems = selectedComposerItems();
1813  if ( selectedItems.size() < 2 )
1814  {
1815  return;
1816  }
1817 
1818  QRectF selectedItemBBox;
1819  if ( boundingRectOfSelectedItems( selectedItemBBox ) != 0 )
1820  {
1821  return;
1822  }
1823 
1824  double averageYCoord = ( selectedItemBBox.top() + selectedItemBBox.bottom() ) / 2.0;
1825  QUndoCommand *parentCommand = new QUndoCommand( tr( "Aligned items vertical center" ) );
1826  QList<QgsComposerItem *>::iterator align_it = selectedItems.begin();
1827  for ( ; align_it != selectedItems.end(); ++align_it )
1828  {
1829  QgsComposerItemCommand *subcommand = new QgsComposerItemCommand( *align_it, QLatin1String( "" ), parentCommand );
1830  subcommand->savePreviousState();
1831  ( *align_it )->setPos( ( *align_it )->pos().x(), averageYCoord - ( *align_it )->rect().height() / 2 );
1832  subcommand->saveAfterState();
1833  }
1834  mUndoStack->push( parentCommand );
1835  mProject->setDirty( true );
1836 }
1837 
1839 {
1840  QList<QgsComposerItem *> selectedItems = selectedComposerItems();
1841  if ( selectedItems.size() < 2 )
1842  {
1843  return;
1844  }
1845 
1846  QRectF selectedItemBBox;
1847  if ( boundingRectOfSelectedItems( selectedItemBBox ) != 0 )
1848  {
1849  return;
1850  }
1851 
1852  double maxYCoord = selectedItemBBox.bottom();
1853  QUndoCommand *parentCommand = new QUndoCommand( tr( "Aligned items bottom" ) );
1854  QList<QgsComposerItem *>::iterator align_it = selectedItems.begin();
1855  for ( ; align_it != selectedItems.end(); ++align_it )
1856  {
1857  QgsComposerItemCommand *subcommand = new QgsComposerItemCommand( *align_it, QLatin1String( "" ), parentCommand );
1858  subcommand->savePreviousState();
1859  ( *align_it )->setPos( ( *align_it )->pos().x(), maxYCoord - ( *align_it )->rect().height() );
1860  subcommand->saveAfterState();
1861  }
1862  mUndoStack->push( parentCommand );
1863  mProject->setDirty( true );
1864 }
1865 
1867 {
1868  QUndoCommand *parentCommand = new QUndoCommand( tr( "Items locked" ) );
1869  QList<QgsComposerItem *> selectionList = selectedComposerItems();
1870  QList<QgsComposerItem *>::iterator itemIter = selectionList.begin();
1871  for ( ; itemIter != selectionList.end(); ++itemIter )
1872  {
1873  QgsComposerItemCommand *subcommand = new QgsComposerItemCommand( *itemIter, QLatin1String( "" ), parentCommand );
1874  subcommand->savePreviousState();
1875  ( *itemIter )->setPositionLock( true );
1876  subcommand->saveAfterState();
1877  }
1878 
1879  setAllDeselected();
1880  mUndoStack->push( parentCommand );
1881  mProject->setDirty( true );
1882 }
1883 
1885 {
1886  //unlock all items in composer
1887 
1888  QUndoCommand *parentCommand = new QUndoCommand( tr( "Items unlocked" ) );
1889 
1890  //first, clear the selection
1891  setAllDeselected();
1892 
1893  QList<QGraphicsItem *> itemList = items();
1894  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
1895  for ( ; itemIt != itemList.end(); ++itemIt )
1896  {
1897  QgsComposerItem *mypItem = dynamic_cast<QgsComposerItem *>( *itemIt );
1898  if ( mypItem && mypItem->positionLock() )
1899  {
1900  QgsComposerItemCommand *subcommand = new QgsComposerItemCommand( mypItem, QLatin1String( "" ), parentCommand );
1901  subcommand->savePreviousState();
1902  mypItem->setPositionLock( false );
1903  //select unlocked items, same behavior as illustrator
1904  mypItem->setSelected( true );
1905  emit selectedItemChanged( mypItem );
1906  subcommand->saveAfterState();
1907  }
1908  }
1909  mUndoStack->push( parentCommand );
1910  mProject->setDirty( true );
1911 }
1912 
1913 QgsComposerItemGroup *QgsComposition::groupItems( QList<QgsComposerItem *> items )
1914 {
1915  if ( items.size() < 2 )
1916  {
1917  //not enough items for a group
1918  return nullptr;
1919  }
1920 
1921  QgsComposerItemGroup *itemGroup = new QgsComposerItemGroup( this );
1922  QgsDebugMsg( QString( "itemgroup created with %1 items (%2 to be added)" ) .arg( itemGroup->items().size() ).arg( items.size() ) );
1923 
1924  QList<QgsComposerItem *>::iterator itemIter = items.begin();
1925  for ( ; itemIter != items.end(); ++itemIter )
1926  {
1927  itemGroup->addItem( *itemIter );
1928  QgsDebugMsg( QString( "itemgroup now has %1" )
1929  .arg( itemGroup->items().size() ) );
1930  }
1931 
1932  addItem( itemGroup );
1933 
1934  QgsGroupUngroupItemsCommand *c = new QgsGroupUngroupItemsCommand( QgsGroupUngroupItemsCommand::Grouped, itemGroup, this, tr( "Items grouped" ) );
1937 
1938  undoStack()->push( c );
1939  mProject->setDirty( true );
1940  //QgsDebugMsg( QString( "itemgroup after pushAddRemove has %1" ) .arg( itemGroup->items().size() ) );
1941 
1942  emit composerItemGroupAdded( itemGroup );
1943 
1944  return itemGroup;
1945 }
1946 
1947 QList<QgsComposerItem *> QgsComposition::ungroupItems( QgsComposerItemGroup *group )
1948 {
1949  QList<QgsComposerItem *> ungroupedItems;
1950  if ( !group )
1951  {
1952  return ungroupedItems;
1953  }
1954 
1955  // group ownership transferred to QgsGroupUngroupItemsCommand
1956  // Call this before removing group items so it can keep note
1957  // of contents
1961 
1962  undoStack()->push( c );
1963  mProject->setDirty( true );
1964 
1965 
1966  QSet<QgsComposerItem *> groupedItems = group->items();
1967  QSet<QgsComposerItem *>::iterator itemIt = groupedItems.begin();
1968  for ( ; itemIt != groupedItems.end(); ++itemIt )
1969  {
1970  ungroupedItems << ( *itemIt );
1971  }
1972 
1973  group->removeItems();
1974 
1975  // note: emits itemRemoved
1976  removeComposerItem( group, false, false );
1977 
1978  return ungroupedItems;
1979 }
1980 
1981 void QgsComposition::updateZValues( const bool addUndoCommands )
1982 {
1983  int counter = mItemsModel->zOrderListSize();
1984  QList<QgsComposerItem *>::const_iterator it = mItemsModel->zOrderList()->constBegin();
1985  QgsComposerItem *currentItem = nullptr;
1986 
1987  QUndoCommand *parentCommand = nullptr;
1988  if ( addUndoCommands )
1989  {
1990  parentCommand = new QUndoCommand( tr( "Item z-order changed" ) );
1991  }
1992  for ( ; it != mItemsModel->zOrderList()->constEnd(); ++it )
1993  {
1994  currentItem = *it;
1995  if ( currentItem )
1996  {
1997  QgsComposerItemCommand *subcommand = nullptr;
1998  if ( addUndoCommands )
1999  {
2000  subcommand = new QgsComposerItemCommand( *it, QLatin1String( "" ), parentCommand );
2001  subcommand->savePreviousState();
2002  }
2003  currentItem->setZValue( counter );
2004  if ( addUndoCommands )
2005  {
2006  subcommand->saveAfterState();
2007  }
2008  }
2009  --counter;
2010  }
2011  if ( addUndoCommands )
2012  {
2013  mUndoStack->push( parentCommand );
2014  mProject->setDirty( true );
2015  }
2016 }
2017 
2019 {
2020  //model handles changes to item z order list
2021  mItemsModel->rebuildZList();
2022 
2023  //Finally, rebuild the zValue of all items to remove any duplicate zValues and make sure there's
2024  //no missing zValues.
2025  updateZValues( false );
2026 }
2027 
2028 QPointF QgsComposition::snapPointToGrid( QPointF scenePoint ) const
2029 {
2030  if ( !mSnapToGrid || mSnapGridResolution <= 0 || !graphicsView() )
2031  {
2032  return scenePoint;
2033  }
2034 
2035  //y offset to current page
2036  int pageNr = static_cast< int >( scenePoint.y() / ( mPageHeight + mSpaceBetweenPages ) );
2037  double yOffset = pageNr * ( mPageHeight + mSpaceBetweenPages );
2038  double yPage = scenePoint.y() - yOffset; //y-coordinate relative to current page
2039 
2040  //snap x coordinate
2041  int xRatio = static_cast< int >( ( scenePoint.x() - mSnapGridOffsetX ) / mSnapGridResolution + 0.5 ); //NOLINT
2042  int yRatio = static_cast< int >( ( yPage - mSnapGridOffsetY ) / mSnapGridResolution + 0.5 ); //NOLINT
2043 
2044  double xSnapped = xRatio * mSnapGridResolution + mSnapGridOffsetX;
2045  double ySnapped = yRatio * mSnapGridResolution + mSnapGridOffsetY + yOffset;
2046 
2047  //convert snap tolerance from pixels to mm
2048  double viewScaleFactor = graphicsView()->transform().m11();
2049  double alignThreshold = mSnapTolerance / viewScaleFactor;
2050 
2051  if ( std::fabs( xSnapped - scenePoint.x() ) > alignThreshold )
2052  {
2053  //snap distance is outside of tolerance
2054  xSnapped = scenePoint.x();
2055  }
2056  if ( std::fabs( ySnapped - scenePoint.y() ) > alignThreshold )
2057  {
2058  //snap distance is outside of tolerance
2059  ySnapped = scenePoint.y();
2060  }
2061 
2062  return QPointF( xSnapped, ySnapped );
2063 }
2064 
2065 QGraphicsLineItem *QgsComposition::addSnapLine()
2066 {
2067  QGraphicsLineItem *item = new QGraphicsLineItem();
2068  QPen linePen( Qt::SolidLine );
2069  linePen.setColor( Qt::red );
2070  // use a pen width of 0, since this activates a cosmetic pen
2071  // which doesn't scale with the composer and keeps a constant size
2072  linePen.setWidthF( 0 );
2073  item->setPen( linePen );
2074  item->setZValue( 100 );
2075  item->setVisible( mGuidesVisible );
2076  addItem( item );
2077  mSnapLines.push_back( item );
2078  return item;
2079 }
2080 
2081 void QgsComposition::removeSnapLine( QGraphicsLineItem *line )
2082 {
2083  removeItem( line );
2084  mSnapLines.removeAll( line );
2085  delete line;
2086 }
2087 
2089 {
2090  Q_FOREACH ( QGraphicsLineItem *line, mSnapLines )
2091  {
2092  removeItem( line );
2093  delete ( line );
2094  }
2095  mSnapLines.clear();
2096 }
2097 
2098 void QgsComposition::setSnapLinesVisible( const bool visible )
2099 {
2100  mGuidesVisible = visible;
2101  Q_FOREACH ( QGraphicsLineItem *line, mSnapLines )
2102  {
2103  line->setVisible( visible );
2104  }
2105 }
2106 
2108 {
2109  mPagesVisible = visible;
2110  update();
2111 }
2112 
2113 QGraphicsLineItem *QgsComposition::nearestSnapLine( const bool horizontal, const double x, const double y, const double tolerance,
2114  QList< QPair< QgsComposerItem *, QgsComposerItem::ItemPositionMode> > &snappedItems ) const
2115 {
2116  double minSqrDist = DBL_MAX;
2117  QGraphicsLineItem *item = nullptr;
2118  double currentXCoord = 0;
2119  double currentYCoord = 0;
2120  double currentSqrDist = 0;
2121  double sqrTolerance = tolerance * tolerance;
2122 
2123  snappedItems.clear();
2124 
2125  QList< QGraphicsLineItem * >::const_iterator it = mSnapLines.constBegin();
2126  for ( ; it != mSnapLines.constEnd(); ++it )
2127  {
2128  bool itemHorizontal = qgsDoubleNear( ( *it )->line().y2() - ( *it )->line().y1(), 0 );
2129  if ( horizontal && itemHorizontal )
2130  {
2131  currentYCoord = ( *it )->line().y1();
2132  currentSqrDist = ( y - currentYCoord ) * ( y - currentYCoord );
2133  }
2134  else if ( !horizontal && !itemHorizontal )
2135  {
2136  currentXCoord = ( *it )->line().x1();
2137  currentSqrDist = ( x - currentXCoord ) * ( x - currentXCoord );
2138  }
2139  else
2140  {
2141  continue;
2142  }
2143 
2144  if ( currentSqrDist < minSqrDist && currentSqrDist < sqrTolerance )
2145  {
2146  item = *it;
2147  minSqrDist = currentSqrDist;
2148  }
2149  }
2150 
2151  double itemTolerance = 0.0000001;
2152  if ( item )
2153  {
2154  //go through all the items to find items snapped to this snap line
2155  QList<QGraphicsItem *> itemList = items();
2156  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
2157  for ( ; itemIt != itemList.end(); ++itemIt )
2158  {
2159  QgsComposerItem *currentItem = dynamic_cast<QgsComposerItem *>( *itemIt );
2160  if ( !currentItem || currentItem->type() == QgsComposerItem::ComposerPaper )
2161  {
2162  continue;
2163  }
2164 
2165  if ( horizontal )
2166  {
2167  if ( qgsDoubleNear( currentYCoord, currentItem->pos().y() + currentItem->rect().top(), itemTolerance ) )
2168  {
2169  snappedItems.append( qMakePair( currentItem, QgsComposerItem::UpperMiddle ) );
2170  }
2171  else if ( qgsDoubleNear( currentYCoord, currentItem->pos().y() + currentItem->rect().center().y(), itemTolerance ) )
2172  {
2173  snappedItems.append( qMakePair( currentItem, QgsComposerItem::Middle ) );
2174  }
2175  else if ( qgsDoubleNear( currentYCoord, currentItem->pos().y() + currentItem->rect().bottom(), itemTolerance ) )
2176  {
2177  snappedItems.append( qMakePair( currentItem, QgsComposerItem::LowerMiddle ) );
2178  }
2179  }
2180  else
2181  {
2182  if ( qgsDoubleNear( currentXCoord, currentItem->pos().x(), itemTolerance ) )
2183  {
2184  snappedItems.append( qMakePair( currentItem, QgsComposerItem::MiddleLeft ) );
2185  }
2186  else if ( qgsDoubleNear( currentXCoord, currentItem->pos().x() + currentItem->rect().center().x(), itemTolerance ) )
2187  {
2188  snappedItems.append( qMakePair( currentItem, QgsComposerItem::Middle ) );
2189  }
2190  else if ( qgsDoubleNear( currentXCoord, currentItem->pos().x() + currentItem->rect().width(), itemTolerance ) )
2191  {
2192  snappedItems.append( qMakePair( currentItem, QgsComposerItem::MiddleRight ) );
2193  }
2194  }
2195  }
2196  }
2197 
2198  return item;
2199 }
2200 
2201 int QgsComposition::boundingRectOfSelectedItems( QRectF &bRect )
2202 {
2203  QList<QgsComposerItem *> selectedItems = selectedComposerItems();
2204  if ( selectedItems.empty() )
2205  {
2206  return 1;
2207  }
2208 
2209  //set the box to the first item
2210  QgsComposerItem *currentItem = selectedItems.at( 0 );
2211  double minX = currentItem->pos().x();
2212  double minY = currentItem->pos().y();
2213  double maxX = minX + currentItem->rect().width();
2214  double maxY = minY + currentItem->rect().height();
2215 
2216  double currentMinX, currentMinY, currentMaxX, currentMaxY;
2217 
2218  for ( int i = 1; i < selectedItems.size(); ++i )
2219  {
2220  currentItem = selectedItems.at( i );
2221  currentMinX = currentItem->pos().x();
2222  currentMinY = currentItem->pos().y();
2223  currentMaxX = currentMinX + currentItem->rect().width();
2224  currentMaxY = currentMinY + currentItem->rect().height();
2225 
2226  if ( currentMinX < minX )
2227  minX = currentMinX;
2228  if ( currentMaxX > maxX )
2229  maxX = currentMaxX;
2230  if ( currentMinY < minY )
2231  minY = currentMinY;
2232  if ( currentMaxY > maxY )
2233  maxY = currentMaxY;
2234  }
2235 
2236  bRect.setTopLeft( QPointF( minX, minY ) );
2237  bRect.setBottomRight( QPointF( maxX, maxY ) );
2238  return 0;
2239 }
2240 
2242 {
2243  mSnapToGrid = b;
2244  updatePaperItems();
2245 }
2246 
2248 {
2249  mGridVisible = b;
2250  updatePaperItems();
2251 }
2252 
2254 {
2255  mSnapGridResolution = r;
2256  updatePaperItems();
2257 }
2258 
2259 void QgsComposition::setSnapGridOffsetX( const double offset )
2260 {
2261  mSnapGridOffsetX = offset;
2262  updatePaperItems();
2263 }
2264 
2265 void QgsComposition::setSnapGridOffsetY( const double offset )
2266 {
2267  mSnapGridOffsetY = offset;
2268  updatePaperItems();
2269 }
2270 
2271 void QgsComposition::setGridPen( const QPen &p )
2272 {
2273  mGridPen = p;
2274  //make sure grid is drawn using a zero-width cosmetic pen
2275  mGridPen.setWidthF( 0 );
2276  updatePaperItems();
2277 }
2278 
2280 {
2281  mGridStyle = s;
2282  updatePaperItems();
2283 }
2284 
2285 void QgsComposition::setBoundingBoxesVisible( const bool boundsVisible )
2286 {
2287  mBoundingBoxesVisible = boundsVisible;
2288 
2289  if ( mSelectionHandles )
2290  {
2291  mSelectionHandles->update();
2292  }
2293 }
2294 
2296 {
2297  //load new composer setting values
2298  loadSettings();
2299  //update any paper items to reflect new settings
2300  updatePaperItems();
2301 }
2302 
2303 void QgsComposition::loadSettings()
2304 {
2305  //read grid style, grid color and pen width from settings
2306  QgsSettings s;
2307 
2308  QString gridStyleString;
2309  gridStyleString = s.value( QStringLiteral( "/Composer/gridStyle" ), "Dots" ).toString();
2310 
2311  int gridRed, gridGreen, gridBlue, gridAlpha;
2312  gridRed = s.value( QStringLiteral( "/Composer/gridRed" ), 190 ).toInt();
2313  gridGreen = s.value( QStringLiteral( "/Composer/gridGreen" ), 190 ).toInt();
2314  gridBlue = s.value( QStringLiteral( "/Composer/gridBlue" ), 190 ).toInt();
2315  gridAlpha = s.value( QStringLiteral( "/Composer/gridAlpha" ), 100 ).toInt();
2316  QColor gridColor = QColor( gridRed, gridGreen, gridBlue, gridAlpha );
2317 
2318  mGridPen.setColor( gridColor );
2319  mGridPen.setWidthF( 0 );
2320 
2321  if ( gridStyleString == QLatin1String( "Dots" ) )
2322  {
2323  mGridStyle = Dots;
2324  }
2325  else if ( gridStyleString == QLatin1String( "Crosses" ) )
2326  {
2327  mGridStyle = Crosses;
2328  }
2329  else
2330  {
2331  mGridStyle = Solid;
2332  }
2333 }
2334 
2335 void QgsComposition::beginCommand( QgsComposerItem *item, const QString &commandText, const QgsComposerMergeCommand::Context c )
2336 {
2337  delete mActiveItemCommand;
2338  if ( !item )
2339  {
2340  mActiveItemCommand = nullptr;
2341  return;
2342  }
2343 
2345  {
2346  mActiveItemCommand = new QgsComposerItemCommand( item, commandText );
2347  }
2348  else
2349  {
2350  mActiveItemCommand = new QgsComposerMergeCommand( c, item, commandText );
2351  }
2352  mActiveItemCommand->savePreviousState();
2353 }
2354 
2356 {
2357  if ( mActiveItemCommand )
2358  {
2359  mActiveItemCommand->saveAfterState();
2360  if ( mActiveItemCommand->containsChange() ) //protect against empty commands
2361  {
2362  mUndoStack->push( mActiveItemCommand );
2363  mProject->setDirty( true );
2364  }
2365  else
2366  {
2367  delete mActiveItemCommand;
2368  }
2369  mActiveItemCommand = nullptr;
2370  }
2371 }
2372 
2374 {
2375  delete mActiveItemCommand;
2376  mActiveItemCommand = nullptr;
2377 }
2378 
2380 {
2381  delete mActiveMultiFrameCommand;
2382 
2383  if ( !multiFrame )
2384  {
2385  mActiveMultiFrameCommand = nullptr;
2386  return;
2387  }
2388 
2390  {
2391  mActiveMultiFrameCommand = new QgsComposerMultiFrameCommand( multiFrame, text );
2392  }
2393  else
2394  {
2395  mActiveMultiFrameCommand = new QgsComposerMultiFrameMergeCommand( c, multiFrame, text );
2396  }
2397  mActiveMultiFrameCommand->savePreviousState();
2398 }
2399 
2401 {
2402  if ( mActiveMultiFrameCommand )
2403  {
2404  mActiveMultiFrameCommand->saveAfterState();
2405  if ( mActiveMultiFrameCommand->containsChange() )
2406  {
2407  mUndoStack->push( mActiveMultiFrameCommand );
2408  mProject->setDirty( true );
2409  }
2410  else
2411  {
2412  delete mActiveMultiFrameCommand;
2413  }
2414  mActiveMultiFrameCommand = nullptr;
2415  }
2416 }
2417 
2419 {
2420  delete mActiveMultiFrameCommand;
2421  mActiveMultiFrameCommand = nullptr;
2422 }
2423 
2425 {
2426  mMultiFrames.insert( multiFrame );
2427 
2428  updateBounds();
2429 }
2430 
2432 {
2433  mMultiFrames.remove( multiFrame );
2434 
2435  updateBounds();
2436 }
2437 
2439 {
2440  addItem( arrow );
2441 
2442  updateBounds();
2444 
2445  emit itemAdded( arrow );
2446 }
2447 
2449 {
2450  addItem( polygon );
2451 
2452  updateBounds();
2453  connect( polygon, &QgsComposerItem::sizeChanged, this, &QgsComposition::updateBounds );
2454 
2455  emit itemAdded( polygon );
2456 }
2457 
2459 {
2460  addItem( polyline );
2461 
2462  updateBounds();
2463  connect( polyline, &QgsComposerItem::sizeChanged, this, &QgsComposition::updateBounds );
2464 
2465  emit itemAdded( polyline );
2466 }
2467 
2469 {
2470  addItem( label );
2471 
2472  updateBounds();
2474 
2475  emit itemAdded( label );
2476 }
2477 
2479 {
2480  addItem( map );
2481  updateBounds();
2483 
2484  emit itemAdded( map );
2485 }
2486 
2488 {
2489  addItem( scaleBar );
2490 
2491  updateBounds();
2492  connect( scaleBar, &QgsComposerItem::sizeChanged, this, &QgsComposition::updateBounds );
2493 
2494  emit itemAdded( scaleBar );
2495 }
2496 
2498 {
2499  addItem( legend );
2500 
2501  updateBounds();
2502  connect( legend, &QgsComposerItem::sizeChanged, this, &QgsComposition::updateBounds );
2503 
2504  emit itemAdded( legend );
2505 }
2506 
2508 {
2509  addItem( picture );
2510 
2511  updateBounds();
2512  connect( picture, &QgsComposerItem::sizeChanged, this, &QgsComposition::updateBounds );
2513 
2514  emit itemAdded( picture );
2515 }
2516 
2518 {
2519  addItem( shape );
2520 
2521  updateBounds();
2523 
2524  emit itemAdded( shape );
2525 }
2526 
2528 {
2529  addItem( frame );
2530 
2531  updateBounds();
2533 
2534  emit itemAdded( frame );
2535 }
2536 
2538 {
2539  addItem( frame );
2540 
2541  updateBounds();
2543 
2544  emit itemAdded( frame );
2545 }
2546 
2547 /* public */
2548 void QgsComposition::removeComposerItem( QgsComposerItem *item, const bool createCommand, const bool removeGroupItems )
2549 {
2550  QgsComposerMap *map = dynamic_cast<QgsComposerMap *>( item );
2551 
2552  if ( !map || !map->isDrawing() ) //don't delete a composer map while it draws
2553  {
2554  mItemsModel->setItemRemoved( item );
2555  removeItem( item );
2556  emit itemRemoved( item );
2557 
2558  QgsDebugMsg( QString( "removeComposerItem called, createCommand:%1 removeGroupItems:%2" )
2559  .arg( createCommand ).arg( removeGroupItems ) );
2560 
2561  QgsComposerItemGroup *itemGroup = dynamic_cast<QgsComposerItemGroup *>( item );
2562  if ( itemGroup && removeGroupItems )
2563  {
2564  QgsDebugMsg( QString( "itemGroup && removeGroupItems" ) );
2565 
2566  // Takes ownership of itemGroup
2567  QgsAddRemoveItemCommand *parentCommand = new QgsAddRemoveItemCommand(
2568  QgsAddRemoveItemCommand::Removed, itemGroup, this,
2569  tr( "Remove item group" ) );
2570  connectAddRemoveCommandSignals( parentCommand );
2571 
2572  //add add/remove item command for every item in the group
2573  QSet<QgsComposerItem *> groupedItems = itemGroup->items();
2574  QgsDebugMsg( QString( "itemGroup contains %1 items" ) .arg( groupedItems.size() ) );
2575  QSet<QgsComposerItem *>::iterator it = groupedItems.begin();
2576  for ( ; it != groupedItems.end(); ++it )
2577  {
2578  mItemsModel->setItemRemoved( *it );
2579  removeItem( *it );
2580  QgsAddRemoveItemCommand *subcommand = new QgsAddRemoveItemCommand( QgsAddRemoveItemCommand::Removed, *it, this, QLatin1String( "" ), parentCommand );
2581  connectAddRemoveCommandSignals( subcommand );
2582  emit itemRemoved( *it );
2583  }
2584 
2585  undoStack()->push( parentCommand );
2586  }
2587  else
2588  {
2589  bool frameItem = ( item->type() == QgsComposerItem::ComposerFrame );
2590  QgsComposerMultiFrame *multiFrame = nullptr;
2591  if ( createCommand )
2592  {
2593  if ( frameItem ) //multiframe tracks item changes
2594  {
2595  multiFrame = static_cast<QgsComposerFrame *>( item )->multiFrame();
2596  item->beginItemCommand( tr( "Frame deleted" ) );
2597  item->endItemCommand();
2598  }
2599  else
2600  {
2601  pushAddRemoveCommand( item, tr( "Item deleted" ), QgsAddRemoveItemCommand::Removed );
2602  }
2603  }
2604 
2605  //check if there are frames left. If not, remove the multi frame
2606  if ( frameItem && multiFrame )
2607  {
2608  if ( multiFrame->frameCount() < 1 )
2609  {
2610  removeMultiFrame( multiFrame );
2611  if ( createCommand )
2612  {
2614  multiFrame, this, tr( "Multiframe removed" ) );
2615  undoStack()->push( command );
2616  }
2617  else
2618  {
2619  delete multiFrame;
2620  }
2621  }
2622  }
2623  }
2624  }
2625 
2626  updateBounds();
2627 }
2628 
2630 {
2631  QgsAddRemoveItemCommand *c = new QgsAddRemoveItemCommand( state, item, this, text );
2632  connectAddRemoveCommandSignals( c );
2633  undoStack()->push( c );
2634  mProject->setDirty( true );
2635 }
2636 
2637 void QgsComposition::connectAddRemoveCommandSignals( QgsAddRemoveItemCommand *c )
2638 {
2639  if ( !c )
2640  {
2641  return;
2642  }
2643 
2646 }
2647 
2649 {
2650  //cast and send proper signal
2651  item->setSelected( true );
2652  switch ( item->type() )
2653  {
2664 
2665  emit itemAdded( item );
2666  emit selectedItemChanged( item );
2667  return;
2668 
2669  }
2670 
2671  QgsComposerItemGroup *group = dynamic_cast<QgsComposerItemGroup *>( item );
2672  if ( group )
2673  {
2674  emit composerItemGroupAdded( group );
2675  }
2676 }
2677 
2678 void QgsComposition::updatePaperItems()
2679 {
2680  Q_FOREACH ( QgsPaperItem *page, mPages )
2681  {
2682  page->update();
2683  }
2684 }
2685 
2686 void QgsComposition::addPaperItem()
2687 {
2688  double paperHeight = this->paperHeight();
2689  double paperWidth = this->paperWidth();
2690  double currentY = paperHeight * mPages.size() + mPages.size() * mSpaceBetweenPages; //add 10mm visible space between pages
2691  QgsPaperItem *paperItem = new QgsPaperItem( 0, currentY, paperWidth, paperHeight, this ); //default size A4
2692  paperItem->setBrush( Qt::white );
2693  addItem( paperItem );
2694  paperItem->setZValue( 0 );
2695  mPages.push_back( paperItem );
2696 }
2697 
2698 void QgsComposition::removePaperItems()
2699 {
2700  qDeleteAll( mPages );
2701  mPages.clear();
2702 }
2703 
2704 void QgsComposition::deleteAndRemoveMultiFrames()
2705 {
2706  qDeleteAll( mMultiFrames );
2707  mMultiFrames.clear();
2708 }
2709 
2710 #ifndef QT_NO_PRINTER
2711 
2712 void QgsComposition::beginPrintAsPDF( QPrinter &printer, const QString &file )
2713 {
2714  printer.setOutputFileName( file );
2715  // setOutputFormat should come after setOutputFileName, which auto-sets format to QPrinter::PdfFormat.
2716  // [LS] This should be QPrinter::NativeFormat for Mac, otherwise fonts are not embed-able
2717  // and text is not searchable; however, there are several bugs with <= Qt 4.8.5, 5.1.1, 5.2.0:
2718  // https://bugreports.qt-project.org/browse/QTBUG-10094 - PDF font embedding fails
2719  // https://bugreports.qt-project.org/browse/QTBUG-33583 - PDF output converts text to outline
2720  // Also an issue with PDF paper size using QPrinter::NativeFormat on Mac (always outputs portrait letter-size)
2721  printer.setOutputFormat( QPrinter::PdfFormat );
2722 
2723  refreshPageSize();
2724  //must set orientation to portrait before setting paper size, otherwise size will be flipped
2725  //for landscape sized outputs (#11352)
2726  printer.setOrientation( QPrinter::Portrait );
2727  printer.setPaperSize( QSizeF( paperWidth(), paperHeight() ), QPrinter::Millimeter );
2728 
2729  // TODO: add option for this in Composer
2730  // May not work on Windows or non-X11 Linux. Works fine on Mac using QPrinter::NativeFormat
2731  //printer.setFontEmbeddingEnabled( true );
2732 
2733  QgsPaintEngineHack::fixEngineFlags( printer.paintEngine() );
2734 }
2735 
2736 bool QgsComposition::exportAsPDF( const QString &file )
2737 {
2738  QPrinter printer;
2739  beginPrintAsPDF( printer, file );
2740  return print( printer );
2741 }
2742 
2743 #endif
2744 
2745 void QgsComposition::georeferenceOutput( const QString &file, QgsComposerMap *map,
2746  const QRectF &exportRegion, double dpi ) const
2747 {
2748  if ( !map )
2749  map = referenceMap();
2750 
2751  if ( !map )
2752  return; // no reference map
2753 
2754  if ( dpi < 0 )
2755  dpi = printResolution();
2756 
2757  double *t = computeGeoTransform( map, exportRegion, dpi );
2758  if ( !t )
2759  return;
2760 
2761  // important - we need to manually specify the DPI in advance, as GDAL will otherwise
2762  // assume a DPI of 150
2763  CPLSetConfigOption( "GDAL_PDF_DPI", QString::number( dpi ).toLocal8Bit().constData() );
2764  gdal::dataset_unique_ptr outputDS( GDALOpen( file.toLocal8Bit().constData(), GA_Update ) );
2765  if ( outputDS )
2766  {
2767  GDALSetGeoTransform( outputDS.get(), t );
2768 #if 0
2769  //TODO - metadata can be set here, e.g.:
2770  GDALSetMetadataItem( outputDS, "AUTHOR", "me", nullptr );
2771 #endif
2772  GDALSetProjection( outputDS.get(), map->crs().toWkt().toLocal8Bit().constData() );
2773  }
2774  CPLSetConfigOption( "GDAL_PDF_DPI", nullptr );
2775  delete[] t;
2776 }
2777 
2778 #ifndef QT_NO_PRINTER
2779 
2780 void QgsComposition::doPrint( QPrinter &printer, QPainter &p, bool startNewPage )
2781 {
2782  if ( ddPageSizeActive() )
2783  {
2784  //set the page size again so that data defined page size takes effect
2785  refreshPageSize();
2786  //must set orientation to portrait before setting paper size, otherwise size will be flipped
2787  //for landscape sized outputs (#11352)
2788  printer.setOrientation( QPrinter::Portrait );
2789  printer.setPaperSize( QSizeF( paperWidth(), paperHeight() ), QPrinter::Millimeter );
2790  }
2791 
2792  //QgsComposition starts page numbering at 0
2793  int fromPage = ( printer.fromPage() < 1 ) ? 0 : printer.fromPage() - 1;
2794  int toPage = ( printer.toPage() < 1 ) ? numPages() - 1 : printer.toPage() - 1;
2795 
2796  bool pageExported = false;
2797  if ( mPrintAsRaster )
2798  {
2799  for ( int i = fromPage; i <= toPage; ++i )
2800  {
2801  if ( !shouldExportPage( i + 1 ) )
2802  {
2803  continue;
2804  }
2805  if ( ( pageExported && i > fromPage ) || startNewPage )
2806  {
2807  printer.newPage();
2808  }
2809 
2810  QImage image = printPageAsRaster( i );
2811  if ( !image.isNull() )
2812  {
2813  QRectF targetArea( 0, 0, image.width(), image.height() );
2814  p.drawImage( targetArea, image, targetArea );
2815  }
2816  pageExported = true;
2817  }
2818  }
2819 
2820  if ( !mPrintAsRaster )
2821  {
2822  for ( int i = fromPage; i <= toPage; ++i )
2823  {
2824  if ( !shouldExportPage( i + 1 ) )
2825  {
2826  continue;
2827  }
2828  if ( ( pageExported && i > fromPage ) || startNewPage )
2829  {
2830  printer.newPage();
2831  }
2832  renderPage( &p, i );
2833  pageExported = true;
2834  }
2835  }
2836 }
2837 
2838 void QgsComposition::beginPrint( QPrinter &printer, const bool evaluateDDPageSize )
2839 {
2840  //set resolution based on composer setting
2841  printer.setFullPage( true );
2842  printer.setColorMode( QPrinter::Color );
2843 
2844  //set user-defined resolution
2845  printer.setResolution( printResolution() );
2846 
2847  if ( evaluateDDPageSize && ddPageSizeActive() )
2848  {
2849  //set data defined page size
2850  refreshPageSize();
2851  //must set orientation to portrait before setting paper size, otherwise size will be flipped
2852  //for landscape sized outputs (#11352)
2853  printer.setOrientation( QPrinter::Portrait );
2854  printer.setPaperSize( QSizeF( paperWidth(), paperHeight() ), QPrinter::Millimeter );
2855  }
2856 }
2857 
2858 bool QgsComposition::print( QPrinter &printer, const bool evaluateDDPageSize )
2859 {
2860  beginPrint( printer, evaluateDDPageSize );
2861  QPainter p;
2862  bool ready = p.begin( &printer );
2863  if ( !ready )
2864  {
2865  //error beginning print
2866  return false;
2867  }
2868  doPrint( printer, p );
2869  p.end();
2870  return true;
2871 }
2872 
2873 #endif
2874 
2875 QImage QgsComposition::printPageAsRaster( int page, QSize imageSize, int dpi )
2876 {
2877  int resolution = mPrintResolution;
2878  if ( imageSize.isValid() )
2879  {
2880  //output size in pixels specified, calculate resolution using average of
2881  //derived x/y dpi
2882  resolution = ( imageSize.width() / mPageWidth
2883  + imageSize.height() / mPageHeight ) / 2.0 * 25.4;
2884  }
2885  else if ( dpi > 0 )
2886  {
2887  //dpi overridden by function parameters
2888  resolution = dpi;
2889  }
2890 
2891  int width = imageSize.isValid() ? imageSize.width()
2892  : static_cast< int >( resolution * mPageWidth / 25.4 );
2893  int height = imageSize.isValid() ? imageSize.height()
2894  : static_cast< int >( resolution * mPageHeight / 25.4 );
2895 
2896  QImage image( QSize( width, height ), QImage::Format_ARGB32 );
2897  if ( !image.isNull() )
2898  {
2899  image.setDotsPerMeterX( resolution / 25.4 * 1000 );
2900  image.setDotsPerMeterY( resolution / 25.4 * 1000 );
2901  image.fill( 0 );
2902  QPainter imagePainter( &image );
2903  renderPage( &imagePainter, page );
2904  if ( !imagePainter.isActive() ) return QImage();
2905  }
2906  return image;
2907 }
2908 
2909 QImage QgsComposition::renderRectAsRaster( const QRectF &rect, QSize imageSize, int dpi )
2910 {
2911  int resolution = mPrintResolution;
2912  if ( imageSize.isValid() )
2913  {
2914  //output size in pixels specified, calculate resolution using average of
2915  //derived x/y dpi
2916  resolution = ( imageSize.width() / rect.width()
2917  + imageSize.height() / rect.height() ) / 2.0 * 25.4;
2918  }
2919  else if ( dpi > 0 )
2920  {
2921  //dpi overridden by function parameters
2922  resolution = dpi;
2923  }
2924 
2925  int width = imageSize.isValid() ? imageSize.width()
2926  : static_cast< int >( resolution * rect.width() / 25.4 );
2927  int height = imageSize.isValid() ? imageSize.height()
2928  : static_cast< int >( resolution * rect.height() / 25.4 );
2929 
2930  QImage image( QSize( width, height ), QImage::Format_ARGB32 );
2931  if ( !image.isNull() )
2932  {
2933  image.setDotsPerMeterX( resolution / 25.4 * 1000 );
2934  image.setDotsPerMeterY( resolution / 25.4 * 1000 );
2935  image.fill( Qt::transparent );
2936  QPainter imagePainter( &image );
2937  renderRect( &imagePainter, rect );
2938  if ( !imagePainter.isActive() ) return QImage();
2939  }
2940  return image;
2941 }
2942 
2943 void QgsComposition::renderPage( QPainter *p, int page )
2944 {
2945  if ( mPages.size() <= page )
2946  {
2947  return;
2948  }
2949 
2950  QgsPaperItem *paperItem = mPages.at( page );
2951  if ( !paperItem )
2952  {
2953  return;
2954  }
2955 
2956  QRectF paperRect = QRectF( paperItem->pos().x(), paperItem->pos().y(), paperItem->rect().width(), paperItem->rect().height() );
2957  renderRect( p, paperRect );
2958 }
2959 
2960 void QgsComposition::renderRect( QPainter *p, const QRectF &rect )
2961 {
2962  QPaintDevice *paintDevice = p->device();
2963  if ( !paintDevice )
2964  {
2965  return;
2966  }
2967 
2968  QgsComposition::PlotStyle savedPlotStyle = mPlotStyle;
2969  mPlotStyle = QgsComposition::Print;
2970 
2971  setSnapLinesVisible( false );
2972  //hide background before rendering
2973  setBackgroundBrush( Qt::NoBrush );
2974  render( p, QRectF( 0, 0, paintDevice->width(), paintDevice->height() ), rect );
2975  //show background after rendering
2976  setBackgroundBrush( QColor( 215, 215, 215 ) );
2977  setSnapLinesVisible( true );
2978 
2979  mPlotStyle = savedPlotStyle;
2980 }
2981 
2982 double *QgsComposition::computeGeoTransform( const QgsComposerMap *map, const QRectF &region, double dpi ) const
2983 {
2984  if ( !map )
2985  map = referenceMap();
2986 
2987  if ( !map )
2988  return nullptr;
2989 
2990  if ( dpi < 0 )
2991  dpi = printResolution();
2992 
2993  // calculate region of composition to export (in mm)
2994  QRectF exportRegion = region;
2995  if ( !exportRegion.isValid() )
2996  {
2997  int pageNumber = map->page() - 1;
2998  double pageY = pageNumber * ( mPageHeight + mSpaceBetweenPages );
2999  exportRegion = QRectF( 0, pageY, mPageWidth, mPageHeight );
3000  }
3001 
3002  // map rectangle (in mm)
3003  QRectF mapItemSceneRect = map->mapRectToScene( map->rect() );
3004 
3005  // destination width/height in mm
3006  double outputHeightMM = exportRegion.height();
3007  double outputWidthMM = exportRegion.width();
3008 
3009  // map properties
3010  QgsRectangle mapExtent = *map->currentMapExtent();
3011  double mapXCenter = mapExtent.center().x();
3012  double mapYCenter = mapExtent.center().y();
3013  double alpha = - map->mapRotation() / 180 * M_PI;
3014  double sinAlpha = std::sin( alpha );
3015  double cosAlpha = std::cos( alpha );
3016 
3017  // get the extent (in map units) for the exported region
3018  QPointF mapItemPos = map->pos();
3019  //adjust item position so it is relative to export region
3020  mapItemPos.rx() -= exportRegion.left();
3021  mapItemPos.ry() -= exportRegion.top();
3022 
3023  // calculate extent of entire page in map units
3024  double xRatio = mapExtent.width() / mapItemSceneRect.width();
3025  double yRatio = mapExtent.height() / mapItemSceneRect.height();
3026  double xmin = mapExtent.xMinimum() - mapItemPos.x() * xRatio;
3027  double ymax = mapExtent.yMaximum() + mapItemPos.y() * yRatio;
3028  QgsRectangle paperExtent( xmin, ymax - outputHeightMM * yRatio, xmin + outputWidthMM * xRatio, ymax );
3029 
3030  // calculate origin of page
3031  double X0 = paperExtent.xMinimum();
3032  double Y0 = paperExtent.yMaximum();
3033 
3034  if ( !qgsDoubleNear( alpha, 0.0 ) )
3035  {
3036  // translate origin to account for map rotation
3037  double X1 = X0 - mapXCenter;
3038  double Y1 = Y0 - mapYCenter;
3039  double X2 = X1 * cosAlpha + Y1 * sinAlpha;
3040  double Y2 = -X1 * sinAlpha + Y1 * cosAlpha;
3041  X0 = X2 + mapXCenter;
3042  Y0 = Y2 + mapYCenter;
3043  }
3044 
3045  // calculate scaling of pixels
3046  int pageWidthPixels = static_cast< int >( dpi * outputWidthMM / 25.4 );
3047  int pageHeightPixels = static_cast< int >( dpi * outputHeightMM / 25.4 );
3048  double pixelWidthScale = paperExtent.width() / pageWidthPixels;
3049  double pixelHeightScale = paperExtent.height() / pageHeightPixels;
3050 
3051  // transform matrix
3052  double *t = new double[6];
3053  t[0] = X0;
3054  t[1] = cosAlpha * pixelWidthScale;
3055  t[2] = -sinAlpha * pixelWidthScale;
3056  t[3] = Y0;
3057  t[4] = -sinAlpha * pixelHeightScale;
3058  t[5] = -cosAlpha * pixelHeightScale;
3059 
3060  return t;
3061 }
3062 
3063 QString QgsComposition::encodeStringForXml( const QString &str )
3064 {
3065  QString modifiedStr( str );
3066  modifiedStr.replace( '&', QLatin1String( "&amp;" ) );
3067  modifiedStr.replace( '\"', QLatin1String( "&quot;" ) );
3068  modifiedStr.replace( '\'', QLatin1String( "&apos;" ) );
3069  modifiedStr.replace( '<', QLatin1String( "&lt;" ) );
3070  modifiedStr.replace( '>', QLatin1String( "&gt;" ) );
3071  return modifiedStr;
3072 }
3073 
3074 QGraphicsView *QgsComposition::graphicsView() const
3075 {
3076  //try to find current view attached to composition
3077  QList<QGraphicsView *> viewList = views();
3078  if ( !viewList.isEmpty() )
3079  {
3080  return viewList.at( 0 );
3081  }
3082 
3083  //no view attached to composition
3084  return nullptr;
3085 }
3086 
3087 void QgsComposition::computeWorldFileParameters( double &a, double &b, double &c, double &d, double &e, double &f ) const
3088 {
3089  const QgsComposerMap *map = referenceMap();
3090  if ( !map )
3091  {
3092  return;
3093  }
3094 
3095  int pageNumber = map->page() - 1;
3096  double pageY = pageNumber * ( mPageHeight + mSpaceBetweenPages );
3097  QRectF pageRect( 0, pageY, mPageWidth, mPageHeight );
3098  computeWorldFileParameters( pageRect, a, b, c, d, e, f );
3099 }
3100 
3101 void QgsComposition::computeWorldFileParameters( const QRectF &exportRegion, double &a, double &b, double &c, double &d, double &e, double &f ) const
3102 {
3103  // World file parameters : affine transformation parameters from pixel coordinates to map coordinates
3104  QgsComposerMap *map = referenceMap();
3105  if ( !map )
3106  {
3107  return;
3108  }
3109 
3110  double destinationHeight = exportRegion.height();
3111  double destinationWidth = exportRegion.width();
3112 
3113  QRectF mapItemSceneRect = map->mapRectToScene( map->rect() );
3114  QgsRectangle mapExtent = *map->currentMapExtent();
3115 
3116  double alpha = map->mapRotation() / 180 * M_PI;
3117 
3118  double xRatio = mapExtent.width() / mapItemSceneRect.width();
3119  double yRatio = mapExtent.height() / mapItemSceneRect.height();
3120 
3121  double xCenter = mapExtent.center().x();
3122  double yCenter = mapExtent.center().y();
3123 
3124  // get the extent (in map units) for the region
3125  QPointF mapItemPos = map->pos();
3126  //adjust item position so it is relative to export region
3127  mapItemPos.rx() -= exportRegion.left();
3128  mapItemPos.ry() -= exportRegion.top();
3129 
3130  double xmin = mapExtent.xMinimum() - mapItemPos.x() * xRatio;
3131  double ymax = mapExtent.yMaximum() + mapItemPos.y() * yRatio;
3132  QgsRectangle paperExtent( xmin, ymax - destinationHeight * yRatio, xmin + destinationWidth * xRatio, ymax );
3133 
3134  double X0 = paperExtent.xMinimum();
3135  double Y0 = paperExtent.yMinimum();
3136 
3137  int widthPx = static_cast< int >( printResolution() * destinationWidth / 25.4 );
3138  int heightPx = static_cast< int >( printResolution() * destinationHeight / 25.4 );
3139 
3140  double Ww = paperExtent.width() / widthPx;
3141  double Hh = paperExtent.height() / heightPx;
3142 
3143  // scaling matrix
3144  double s[6];
3145  s[0] = Ww;
3146  s[1] = 0;
3147  s[2] = X0;
3148  s[3] = 0;
3149  s[4] = -Hh;
3150  s[5] = Y0 + paperExtent.height();
3151 
3152  // rotation matrix
3153  double r[6];
3154  r[0] = std::cos( alpha );
3155  r[1] = -std::sin( alpha );
3156  r[2] = xCenter * ( 1 - std::cos( alpha ) ) + yCenter * std::sin( alpha );
3157  r[3] = std::sin( alpha );
3158  r[4] = std::cos( alpha );
3159  r[5] = - xCenter * std::sin( alpha ) + yCenter * ( 1 - std::cos( alpha ) );
3160 
3161  // result = rotation x scaling = rotation(scaling(X))
3162  a = r[0] * s[0] + r[1] * s[3];
3163  b = r[0] * s[1] + r[1] * s[4];
3164  c = r[0] * s[2] + r[1] * s[5] + r[2];
3165  d = r[3] * s[0] + r[4] * s[3];
3166  e = r[3] * s[1] + r[4] * s[4];
3167  f = r[3] * s[2] + r[4] * s[5] + r[5];
3168 }
3169 
3171 {
3172  mAtlasMode = mode;
3173 
3174  if ( mode == QgsComposition::AtlasOff )
3175  {
3176  mAtlasComposition.endRender();
3177  }
3178  else
3179  {
3180  bool atlasHasFeatures = mAtlasComposition.beginRender();
3181  if ( ! atlasHasFeatures )
3182  {
3183  mAtlasMode = QgsComposition::AtlasOff;
3184  mAtlasComposition.endRender();
3185  return false;
3186  }
3187  }
3188 
3189  update();
3190  return true;
3191 }
3192 
3193 bool QgsComposition::ddPageSizeActive() const
3194 {
3195  //check if any data defined page settings are active
3196  return mDataDefinedProperties.isActive( QgsComposerObject::PresetPaperSize ) ||
3197  mDataDefinedProperties.isActive( QgsComposerObject::PaperWidth ) ||
3198  mDataDefinedProperties.isActive( QgsComposerObject::PaperHeight ) ||
3199  mDataDefinedProperties.isActive( QgsComposerObject::PaperOrientation );
3200 }
3201 
3202 void QgsComposition::refreshPageSize( const QgsExpressionContext *context )
3203 {
3205  const QgsExpressionContext *evalContext = context ? context : &scopedContext;
3206 
3207  double pageWidth = mPageWidth;
3208  double pageHeight = mPageHeight;
3209 
3210  //in order of precedence - first consider predefined page size
3211  bool ok = false;
3212  QString presetString = mDataDefinedProperties.valueAsString( QgsComposerObject::PresetPaperSize, *evalContext, QString(), &ok );
3213  if ( ok && !presetString.isEmpty() )
3214  {
3215  double widthD = 0;
3216  double heightD = 0;
3217  if ( QgsComposerUtils::decodePresetPaperSize( presetString, widthD, heightD ) )
3218  {
3219  pageWidth = widthD;
3220  pageHeight = heightD;
3221  }
3222  }
3223 
3224  //which is overwritten by data defined width/height
3225  pageWidth = mDataDefinedProperties.valueAsDouble( QgsComposerObject::PaperWidth, *evalContext, pageWidth );
3226  pageHeight = mDataDefinedProperties.valueAsDouble( QgsComposerObject::PaperHeight, *evalContext, pageHeight );
3227 
3228  //which is finally overwritten by data defined orientation
3229  QString orientationString = mDataDefinedProperties.valueAsString( QgsComposerObject::PaperOrientation, *evalContext, QString(), &ok );
3230  if ( ok && !orientationString.isEmpty() )
3231  {
3232  orientationString = orientationString.trimmed();
3233  QgsComposition::PaperOrientation orientation = QgsComposerUtils::decodePaperOrientation( orientationString, ok );
3234  QgsDebugMsg( QString( "exprVal Paper Orientation:%1" ).arg( orientationString ) );
3235  if ( ok )
3236  {
3237  double heightD, widthD;
3238  if ( orientation == QgsComposition::Portrait )
3239  {
3240  heightD = std::max( pageHeight, pageWidth );
3241  widthD = std::min( pageHeight, pageWidth );
3242  }
3243  else
3244  {
3245  heightD = std::min( pageHeight, pageWidth );
3246  widthD = std::max( pageHeight, pageWidth );
3247  }
3248  pageWidth = widthD;
3249  pageHeight = heightD;
3250  }
3251  }
3252 
3253  setPaperSize( pageWidth, pageHeight );
3254 }
3255 
3256 void QgsComposition::setCustomProperty( const QString &key, const QVariant &value )
3257 {
3258  mCustomProperties.setValue( key, value );
3259 
3260  if ( key.startsWith( QLatin1String( "variable" ) ) )
3261  emit variablesChanged();
3262 }
3263 
3264 QVariant QgsComposition::customProperty( const QString &key, const QVariant &defaultValue ) const
3265 {
3266  return mCustomProperties.value( key, defaultValue );
3267 }
3268 
3269 void QgsComposition::removeCustomProperty( const QString &key )
3270 {
3271  mCustomProperties.remove( key );
3272 }
3273 
3275 {
3276  return mCustomProperties.keys();
3277 }
3278 
3280 {
3285  if ( mAtlasComposition.enabled() )
3286  {
3287  context.appendScope( QgsExpressionContextUtils::atlasScope( &mAtlasComposition ) );
3288  }
3289  return context;
3290 }
3291 
3292 void QgsComposition::prepareAllDataDefinedExpressions()
3293 {
3295  mDataDefinedProperties.prepare( context );
3296 }
3297 
void beginPrint(QPrinter &printer, const bool evaluateDDPageSize=false)
Prepare the printer for printing.
void setDirty(bool b=true)
Flag the project as dirty (modified).
Definition: qgsproject.cpp:414
Item representing the paper.
Definition: qgspaperitem.h:46
void composerItemGroupAdded(QgsComposerItemGroup *group)
Is emitted when a new item group has been added to the view.
The class is used as a container of context for various read/write operations on other objects...
A scale bar item that can be added to a map composition.
QUndoStack * undoStack()
Returns pointer to undo/redo command storage.
void unlockAllItems()
Unlock all items.
QgsComposerItemGroup * groupItems(QList< QgsComposerItem *> items)
Creates a new group from a list of composer items and adds it to the composition. ...
A rectangle specified with double values.
Definition: qgsrectangle.h:39
bool readXml(const QDomElement &itemElem, const QDomDocument &doc) override
Sets state from Dom document.
Composer item for polylines.
void setPathResolver(const QgsPathResolver &resolver)
Sets up path resolver for conversion between relative and absolute paths.
const QgsComposerMap * getComposerMapById(const int id) const
Returns the composer map with specified id.
const QgsComposerItem * getComposerItemById(const QString &id) const
Returns a composer item given its text identifier.
void composerItems(QList< T *> &itemList)
Return composer items of a specific type.
void setAllDeselected()
Clears any selected items in the composition.
bool containsChange() const
Returns true if previous state and after state are valid and different.
An item that draws an arrow between two points.
QgsComposerMapOverviewStack * overviews()
Returns the map item&#39;s overview stack, which is used to control how overviews are drawn over the map&#39;...
bool shouldExportPage(const int page) const
Returns whether a specified page number should be included in exports of the composition.
QRectF pageItemBounds(int pageNumber, bool visibleOnly=false) const
Returns the bounding box of the items contained on a specified page.
int itemPageNumber(const QgsComposerItem *) const
Returns on which page number (0-based) is displayed an item.
void addItemToZList(QgsComposerItem *item)
Adds item to z list. Usually called from constructor of QgsComposerItem.
void readXml(const QDomNode &parentNode, const QString &keyStartsWith=QString())
Read store contents from XML.
void setBoundingBoxesVisible(const bool boundsVisible)
Sets whether selection bounding boxes should be shown in the composition.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:55
virtual bool readXml(const QDomElement &collectionElem, const QgsPropertiesDefinition &definitions)
Reads property collection state from an XML element.
static QgsExpressionContextScope * atlasScope(const QgsAtlasComposition *atlas)
Creates a new scope which contains variables and functions relating to a QgsAtlasComposition.
void assignFreeId()
Sets mId to a number not yet used in the composition.
void statusMsgChanged(const QString &message)
Is emitted when the composition has an updated status bar message for the composer window...
QString name() const
Returns the composition&#39;s name.
virtual void beginItemCommand(const QString &text)
void setResizeToContentsMargins(double marginTop, double marginRight, double marginBottom, double marginLeft)
Sets the resize to contents margins.
GridStyle
Style to draw the snapping grid.
#define QgsDebugMsg(str)
Definition: qgslogger.h:37
void alignSelectedItemsTop()
void rebuildZList()
Rebuilds the z-order list, based on the current stacking of items in the composition.
int pageNumberForPoint(QPointF position) const
Returns the page number corresponding to a point in the composition.
bool readXml(const QDomElement &itemElem, const QDomDocument &doc) override
Sets state from Dom document.
double y
Definition: qgspointxy.h:48
static QgsFillSymbol * createSimple(const QgsStringMap &properties)
Create a fill symbol with one symbol layer: SimpleFill with specified properties. ...
Definition: qgssymbol.cpp:1084
void removeItems() override
Removes the items but does not delete them.
int printResolution() const
bool pageIsEmpty(const int page) const
Returns whether a page is empty, ie, it contains no items except for the background paper item...
void addComposerScaleBar(QgsComposerScaleBar *scaleBar)
Adds scale bar to the graphics scene and advises composer to create a widget for it (through signal) ...
A item that forms part of a map composition.
void setSelectedItem(QgsComposerItem *item)
Clears any selected items and sets an item as the current selection.
void setPagesVisible(bool visible)
Sets whether the page items should be visible in the composition.
void pushAddRemoveCommand(QgsComposerItem *item, const QString &text, const QgsAddRemoveItemCommand::State state=QgsAddRemoveItemCommand::Added)
Convenience function to create a QgsAddRemoveItemCommand, connect its signals and push it to the undo...
QSet< QgsComposerItem * > items()
QVariant customProperty(const QString &key, const QVariant &defaultValue=QVariant()) const
Read a custom property from the composition.
void removeItemFromZList(QgsComposerItem *item)
Removes item from z list. Usually called from destructor of QgsComposerItem.
bool readXml(const QDomElement &itemElem, const QDomDocument &doc) override
Sets state from Dom document.
const QgsComposerItem * getComposerItemByUuid(const QString &uuid) const
Returns a composer item given its unique identifier.
QgsComposition(QgsProject *project)
Construct a new composition linked to the specified project.
int id() const
Get identification number.
int numPages() const
Returns the number of pages in the composition.
void updateBounds()
Updates the scene bounds of the composition.
A container for grouping several QgsComposerItems.
void paperSizeChanged()
void sendItemAddedSignal(QgsComposerItem *item)
Casts object to the proper subclass type and calls corresponding itemAdded signal.
QVariant value(const QString &key, const QVariant &defaultValue=QVariant()) const
Return value for the given key. If the key is not stored, default value will be used.
virtual void setSelected(bool s)
Set selected, selected item should be highlighted.
void savePreviousState()
Saves current item state as previous state.
A composer command that merges together with other commands having the same context (=id)...
bool moveItemToBottom(QgsComposerItem *item)
void setCreateUndoCommands(bool enabled)
Sets whether undo commands should be created for interactions with the multiframe.
void writeXml(QDomNode &parentNode, QDomDocument &doc) const
Write store contents to XML.
void toggled(bool)
Emitted when atlas is enabled or disabled.
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
void beginPrintAsPDF(QPrinter &printer, const QString &file)
Prepare the printer for printing in a PDF.
void moveSelectedItemsToBottom()
bool reorderItemDown(QgsComposerItem *item)
Moves an item down the z-order list.
void alignSelectedItemsHCenter()
void doPrint(QPrinter &printer, QPainter &painter, bool startNewPage=false)
Print on a preconfigured printer.
void setPaperSize(double width, double height, bool keepRelativeItemPosition=true)
Changes size of paper item.
void setStatusMessage(const QString &message)
Sets the status bar message for the composer window.
void setName(const QString &name)
Sets the composition&#39;s name.
QMap< QString, QString > QgsStringMap
Definition: qgis.h:443
QList< const QgsComposerMap * > composerMapItems() const
Returns pointers to all composer maps in the scene.
void remove(const QString &key)
Remove a key (entry) from the store.
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Compare two doubles (but allow some difference)
Definition: qgis.h:227
void setGridVisible(const bool b)
void alignSelectedItemsVCenter()
A table that displays attributes from a vector layer.
DataDefinedProperty
Data defined properties for different item types.
bool readXml(const QDomElement &compositionElem, const QDomDocument &doc)
Reads settings from xml file.
void variablesChanged()
Emitted whenever the expression variables stored in the composition have been changed.
void itemAdded(QgsComposerItem *item)
Is emitted when a new composer item has been added to the composition.
void endRender()
Ends the rendering. Restores original extent.
int valueAsInt(int key, const QgsExpressionContext &context, int defaultValue=0, bool *ok=0) const
Calculates the current value of the property with the specified key and interprets it as an integer...
bool isActive(int key) const override
Returns true if the collection contains an active property with the specified key.
A composer class that displays svg files or raster format (jpg, png, ...)
void setCustomProperty(const QString &key, const QVariant &value)
Set a custom property for the composition.
void setItemPosition(double x, double y, ItemPositionMode itemPoint=UpperLeft, int page=-1)
Moves the item to a new position (in canvas coordinates)
QList< QgsComposerItem * > * zOrderList()
Returns the item z-order list.
void addComposerShape(QgsComposerShape *shape)
Adds a composer shape to the graphics scene and advises composer to create a widget for it (through s...
QGraphicsLineItem * nearestSnapLine(const bool horizontal, const double x, const double y, const double tolerance, QList< QPair< QgsComposerItem *, QgsComposerItem::ItemPositionMode > > &snappedItems) const
Get nearest snap line.
void setGridPen(const QPen &p)
void itemAdded(QgsComposerItem *item)
bool readXml(const QDomElement &itemElem, const QDomDocument &doc) override
Sets state from Dom document.
void setUseSymbol(bool useSymbol)
Controls whether the shape should be drawn using a QgsFillSymbol.
int page() const
Gets the page the item is currently on.
virtual bool prepare(const QgsExpressionContext &context=QgsExpressionContext()) const override
Prepares the collection against a specified expression context.
void setNumPages(const int pages)
Sets the number of pages for the composition.
void refreshItemsTriggered()
Is emitted when item in the composition must be refreshed.
QgsComposerItem * getComposerItemAbove(QgsComposerItem *item) const
Finds the next composer item above an item.
bool beginRender()
Begins the rendering.
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 setSnapLinesVisible(const bool visible)
Hides / shows custom snap lines.
bool readXml(const QDomElement &itemElem, const QDomDocument &doc) override
Sets state from DOM document.
QgsComposerItem * getComposerItemBelow(QgsComposerItem *item) const
Finds the next composer item below an item.
bool readXml(const QDomElement &itemElem, const QDomDocument &doc) override
Sets state from Dom document.
void cancelCommand()
Deletes current command.
int pageNumberAt(QPointF position) const
Returns the page number (0-based) given a coordinate.
void setSnapGridOffsetX(const double offset)
QgsPathResolver pathResolver() const
Return path resolver object with considering whether the project uses absolute or relative paths and ...
void endCommand()
Saves end state of item and pushes command to the undo history.
void itemRemoved(QgsComposerItem *)
Is emitted when a composer item has been removed from the scene.
virtual QgsFillSymbol * clone() const override
Get a deep copy of this symbol.
Definition: qgssymbol.cpp:1841
void updatePagePos(double newPageWidth, double newPageHeight)
Moves the item so that it retains its relative position on the page when the paper size changes...
static const QgsPropertiesDefinition & propertyDefinitions()
Returns the composer object property definitions.
void clear()
Clears all items from z-order list and resets the model.
QList< QgsPaperItem *> pages()
Return pages in the correct order.
void removeSnapLine(QGraphicsLineItem *line)
Remove custom snap line (and delete the object)
QgsComposerMap * referenceMap() const
Returns the map item which will be used to generate corresponding world files when the composition is...
double width() const
Returns the width of the rectangle.
Definition: qgsrectangle.h:138
void addItem(QgsComposerItem *item) override
Adds an item to the group.
void alignSelectedItemsRight()
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void georeferenceOutput(const QString &file, QgsComposerMap *referenceMap=nullptr, const QRectF &exportRegion=QRectF(), double dpi=-1) const
Georeferences a file (image of PDF) exported from the composition.
Abstract base class for composer items with the ability to distribute the content to several frames (...
void resizePageToContents(double marginTop=0.0, double marginRight=0.0, double marginBottom=0.0, double marginLeft=0.0)
Resizes the composition page to fit the current contents of the composition.
void refreshDataDefinedProperty(const QgsComposerObject::DataDefinedProperty property=QgsComposerObject::AllProperties, const QgsExpressionContext *context=nullptr)
Refreshes a data defined property for the composition by reevaluating the property&#39;s value and redraw...
bool containsChange() const
Returns true if previous state and after state are valid and different.
void addComposerTableFrame(QgsComposerAttributeTableV2 *table, QgsComposerFrame *frame)
Adds composer tablev2 frame and advises composer to create a widget for it (through signal) ...
QRectF compositionBounds(bool ignorePages=false, double margin=0.0) const
Calculates the bounds of all non-gui items in the composition.
QList< QgsComposerItem * > ungroupItems(QgsComposerItemGroup *group)
Ungroups items by removing them from an item group and removing the group from the composition...
void cancelMultiFrameCommand()
Deletes current multi frame command.
void removeComposerItem(QgsComposerItem *item, const bool createCommand=true, const bool removeGroupItems=true)
Remove item from the graphics scene. Additionally to QGraphicsScene::removeItem, this function consid...
Reads and writes project states.
Definition: qgsproject.h:81
void removeCustomProperty(const QString &key)
Remove a custom property from the composition.
QString uuid() const
Get item identification name.
bool loadFromTemplate(const QDomDocument &doc, QMap< QString, QString > *substitutionMap=nullptr, bool addUndoCommands=false, const bool clearComposition=true)
Load a template document.
void moveSelectedItemsToTop()
bool readXml(const QDomElement &itemElem, const QDomDocument &doc) override
Sets state from Dom document.
bool isDrawing() const
True if a draw is already in progress.
QImage printPageAsRaster(int page, QSize imageSize=QSize(), int dpi=0)
Renders a composer page to an image.
void itemRemoved(QgsComposerItem *item)
Signals removal of an item (the group)
A composer command that merges together with other commands having the same context (=id) for multi f...
void setSnapGridResolution(const double r)
QStringList customProperties() const
Return list of keys stored in custom properties for composition.
QStringList keys() const
Return list of stored keys.
std::unique_ptr< void, GDALDatasetCloser > dataset_unique_ptr
Scoped GDAL dataset.
Definition: qgsogrutils.h:134
bool readXml(const QDomElement &itemElem, const QDomDocument &doc, bool ignoreFrames=false) override
Reads multiframe state information from a DOM element.
void removeMultiFrame(QgsComposerMultiFrame *multiFrame)
Removes multi frame (but does not delete it)
Object representing map window.
Frame item for a composer multiframe item.
A composer command class for grouping / ungrouping composer items.
QgsComposerItem * composerItemAt(QPointF position, const bool ignoreLocked=false) const
Returns the topmost composer item at a specified position.
double x
Definition: qgspointxy.h:47
void featureChanged(QgsFeature *feature)
Is emitted when the current atlas feature changes.
void refreshItems()
Forces items in the composition to refresh.
bool readXml(const QDomElement &itemElem, const QDomDocument &doc) override
Sets state from Dom document.
QPointF positionOnPage(QPointF position) const
Returns the position within a page of a point in the composition.
static void readOldDataDefinedPropertyMap(const QDomElement &itemElem, QgsPropertyCollection &dataDefinedProperties)
Reads all pre 3.0 data defined properties from an XML element.
void composerItemsOnPage(QList< T *> &itemList, const int pageNumber) const
Return composer items of a specific type on a specified page.
void coverageLayerChanged(QgsVectorLayer *layer)
Is emitted when the coverage layer for an atlas changes.
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:126
void nPagesChanged()
void removeItem(QgsComposerItem *item)
Removes an item from the z-order list.
void resizeToContentsMargins(double &marginTop, double &marginRight, double &marginBottom, double &marginLeft) const
Returns the resize to contents margins.
void updateSettings()
Refreshes the composition when composer related options change.
void saveAfterState()
Saves current item state as after state.
static bool decodePresetPaperSize(const QString &presetString, double &width, double &height)
Decodes a string representing a preset page size.
QgsComposerItem * getComposerItemAbove(QgsComposerItem *item) const
bool setAtlasMode(const QgsComposition::AtlasMode mode)
Sets the current atlas mode of the composition.
void setPositionLock(const bool lock)
Locks / unlocks the item position for mouse drags.
void setPrintResolution(const int dpi)
bool print(QPrinter &printer, const bool evaluateDDPageSize=false)
Convenience function that prepares the printer and prints.
void refreshZList()
Rebuilds the z order list by adding any item which are present in the composition but missing from th...
void addComposerPolyline(QgsComposerPolyline *polyline)
Adds a composer polyline and advises composer to create a widget for it (through signal) ...
bool exportAsPDF(const QString &file)
Convenience function that prepares the printer for printing in PDF and prints.
void lockSelectedItems()
Lock the selected items.
void setGridStyle(const GridStyle s)
A composer command class for adding / removing composer items.
void selectNextByZOrder(const ZValueDirection direction)
void addItemsFromXml(const QDomElement &elem, const QDomDocument &doc, bool addUndoCommands=false, QPointF *pos=nullptr, bool pasteInPlace=false)
Add items from XML representation to the graphics scene (for project file reading, pasting items from clipboard)
QgsCoordinateReferenceSystem crs() const
Returns coordinate reference system used for rendering the map.
void clearSnapLines()
Removes all snap lines.
bool reorderItemUp(QgsComposerItem *item)
Moves an item up the z-order list.
Undo command to undo/redo all composer item related changes.
QgsExpressionContext createExpressionContext() const override
Creates an expression context relating to the compositions&#39;s current state.
A composer items that draws common shapes (ellipse, triangle, rectangle)
Composer item for polygons.
virtual void endItemCommand()
void addComposerMap(QgsComposerMap *map)
Adds map to the graphics scene and advises composer to create a widget for it (through signal) ...
int frameCount() const
Returns the number of frames associated with this multiframe.
QgsProject * project() const
The project associated with the composition.
QList< QgsComposerMapOverview *> asList() const
Returns a list of QgsComposerMapOverviews contained by the stack.
QString valueAsString(int key, const QgsExpressionContext &context, const QString &defaultString=QString(), bool *ok=0) const
Calculates the current value of the property with the specified key and interprets it as a string...
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), const Section section=NoSection) const
Returns the value for setting key.
void addComposerHtmlFrame(QgsComposerHtml *html, QgsComposerFrame *frame)
Adds composer html frame and advises composer to create a widget for it (through signal) ...
QPointF snapPointToGrid(QPointF scenePoint) const
Snaps a scene coordinate point to grid.
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
AtlasMode
Composition atlas modes.
void alignSelectedItemsBottom()
static void fixEngineFlags(QPaintEngine *engine)
QString toWkt() const
Returns a WKT representation of this CRS.
void renderRect(QPainter *p, const QRectF &rect)
Renders a portion of the composition to a paint device.
void alignSelectedItemsLeft()
void sizeChanged()
Emitted if the rectangle changes.
static QDomElement saveSymbol(const QString &symbolName, QgsSymbol *symbol, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a symbol definition to XML.
QList< QGraphicsLineItem *> * snapLines()
Returns pointer to snap lines collection.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:116
void selectedItemChanged(QgsComposerItem *selected)
Is emitted when selected item changed. If 0, no item is selected.
void renderPage(QPainter *p, int page)
Renders a full page to a paint device.
QImage renderRectAsRaster(const QRectF &rect, QSize imageSize=QSize(), int dpi=0)
Renders a portion of the composition to an image.
virtual bool readXml(const QDomElement &itemElem, const QDomDocument &doc, bool ignoreFrames=false) override
Reads the properties specific to an attribute table from xml.
A label that can be placed onto a map composition.
void setUseAdvancedEffects(const bool effectsEnabled)
Used to enable or disable advanced effects such as blend modes in a composition.
void readXml(const QDomElement &elem, const QDomDocument &doc)
Reads general atlas settings from xml.
void setEffectsEnabled(const bool effectsEnabled)
Sets whether effects (e.g., blend modes) are enabled for the item.
double paperHeight() const
Height of paper item.
Number of pages in composition.
void addComposerPicture(QgsComposerPicture *picture)
Adds picture to the graphics scene and advises composer to create a widget for it (through signal) ...
QgsAtlasComposition & atlasComposition()
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:121
double paperWidth() const
Width of paper item.
int zOrderListSize() const
Returns the size of the z-order list, which includes items which may have been removed from the compo...
void nameChanged(const QString &name)
Emitted when the composition&#39;s name is changed.
Handles drawing of selection outlines and mouse handles.
double valueAsDouble(int key, const QgsExpressionContext &context, double defaultValue=0.0, bool *ok=0) const
Calculates the current value of the property with the specified key and interprets it as a double...
void setItemRemoved(QgsComposerItem *item)
Marks an item as removed from the composition.
QgsPointXY center() const
Returns the center point of the rectangle.
Definition: qgsrectangle.h:166
friend class QgsComposerModel
QGraphicsLineItem * addSnapLine()
Add a custom snap line (can be horizontal or vertical)
virtual bool writeXml(QDomElement &elem, QDomDocument &doc) const
Stores item state in DOM element.
bool positionLock() const
Returns whether position lock for mouse drags is enabled returns true if item is locked for mouse mov...
void addComposerPolygon(QgsComposerPolygon *polygon)
Adds a composer polygon and advises composer to create a widget for it (through signal) ...
void printResolutionChanged()
Is emitted when the compositions print resolution changes.
double mapRotation(QgsComposerObject::PropertyValueType valueType=QgsComposerObject::EvaluatedValue) const
Returns the rotation used for drawing the map within the composer item, in degrees clockwise...
bool writeXml(QDomElement &composerElem, QDomDocument &doc)
Writes settings to xml (paper dimension)
PlotStyle
Plot type.
void setPageStyleSymbol(QgsFillSymbol *symbol)
Note: added in version 2.1.
void itemRemoved(QgsComposerItem *item)
bool raiseItem(QgsComposerItem *item)
void setSnapToGridEnabled(const bool b)
bool reorderItemToTop(QgsComposerItem *item)
Moves an item to the top of the z-order list.
virtual bool writeXml(QDomElement &collectionElem, const QgsPropertiesDefinition &definitions) const
Writes the current state of the property collection into an XML element.
void move(double dx, double dy)
Moves item in canvas coordinates.
A legend that can be placed onto a map composition.
void itemAdded(QgsComposerItem *item)
Signals addition of an item (the group)
void addComposerLabel(QgsComposerLabel *label)
Adds label to the graphics scene and advises composer to create a widget for it (through signal) ...
void computeWorldFileParameters(double &a, double &b, double &c, double &d, double &e, double &f) const
Compute world file parameters.
void setReferenceMap(QgsComposerMap *map)
Sets the map item which will be used to generate corresponding world files when the composition is ex...
void addMultiFrame(QgsComposerMultiFrame *multiFrame)
Adds multiframe. The object is owned by QgsComposition until removeMultiFrame is called.
void addItemAtTop(QgsComposerItem *item)
Adds an item to the top of the composition z stack.
bool readXml(const QDomElement &itemElem, const QDomDocument &doc) override
Sets state from Dom document.
QString id() const
Get item&#39;s id (which is not necessarly unique)
const QgsRectangle * currentMapExtent() const
Returns a pointer to the current map extent, which is either the original user specified extent or th...
bool lowerItem(QgsComposerItem *item)
QgsComposerItem * getComposerItemBelow(QgsComposerItem *item) const
void addComposerArrow(QgsComposerArrow *arrow)
Adds an arrow item to the graphics scene and advises composer to create a widget for it (through sign...
bool reorderItemToBottom(QgsComposerItem *item)
Moves an item to the bottom of the z-order list.
bool moveItemToTop(QgsComposerItem *item)
double spaceBetweenPages() const
Returns the vertical space between pages in a composer view.
bool enabled() const
Returns whether the atlas generation is enabled.
Preset paper size for composition.
void addComposerLegend(QgsComposerLegend *legend)
Adds legend to the graphics scene and advises composer to create a widget for it (through signal) ...
static QgsComposition::PaperOrientation decodePaperOrientation(const QString &orientationString, bool &ok)
Decodes a string representing a paper orientation.
All properties for item.
double height() const
Returns the height of the rectangle.
Definition: qgsrectangle.h:145
virtual int type() const override
Return correct graphics item type.
void setSnapGridOffsetY(const double offset)
void beginCommand(QgsComposerItem *item, const QString &commandText, const QgsComposerMergeCommand::Context c=QgsComposerMergeCommand::Unknown)
Allocates new item command and saves initial state in it.
QList< QgsComposerItem * > selectedComposerItems(const bool includeLockedItems=true)
Returns list of selected composer items.
static QgsExpressionContextScope * compositionScope(const QgsComposition *composition)
Creates a new scope which contains variables and functions relating to a QgsComposition.
void beginMultiFrameCommand(QgsComposerMultiFrame *multiFrame, const QString &text, const QgsComposerMultiFrameMergeCommand::Context c=QgsComposerMultiFrameMergeCommand::Unknown)