QGIS API Documentation  2.99.0-Master (b698612)
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 
53 #include <QDomDocument>
54 #include <QDomElement>
55 #include <QGraphicsRectItem>
56 #include <QGraphicsView>
57 #include <QPainter>
58 #include <QPrinter>
59 #include <QDir>
60 
61 #include <limits>
62 
63 #include "gdal.h"
64 #include "cpl_conv.h"
65 
67  : QGraphicsScene( nullptr )
68  , mProject( project )
69  , mAtlasComposition( this )
70 {
71  init();
72 }
73 
75 {
76  // these members should be ideally in constructor's initialization list, but now we have two constructors...
77  mPlotStyle = QgsComposition::Preview;
78  mPageWidth = 297;
79  mPageHeight = 210;
80  mSpaceBetweenPages = 10;
81  mPageStyleSymbol = nullptr;
82  mPrintAsRaster = false;
83  mGenerateWorldFile = false;
84  mUseAdvancedEffects = true;
85  mSnapToGrid = false;
86  mGridVisible = false;
87  mPagesVisible = true;
88  mSnapGridResolution = 0;
89  mSnapGridOffsetX = 0;
90  mSnapGridOffsetY = 0;
91  mAlignmentSnap = true;
92  mGuidesVisible = true;
93  mSmartGuides = true;
94  mSnapTolerance = 0;
95  mBoundingBoxesVisible = true;
96  mSelectionHandles = nullptr;
97  mActiveItemCommand = nullptr;
98  mActiveMultiFrameCommand = nullptr;
99  mAtlasMode = QgsComposition::AtlasOff;
100  mPreventCursorChange = false;
101  mItemsModel = nullptr;
102  mUndoStack = new QUndoStack();
103 
104  mResizeToContentsMarginTop = 0;
105  mResizeToContentsMarginRight = 0;
106  mResizeToContentsMarginBottom = 0;
107  mResizeToContentsMarginLeft = 0;
108 
109  //connect to atlas toggling on/off and coverage layer and feature changes
110  //to update data defined values
111  connect( &mAtlasComposition, &QgsAtlasComposition::toggled, this, [this] { refreshDataDefinedProperty(); } );
112  connect( &mAtlasComposition, &QgsAtlasComposition::coverageLayerChanged, this, [this] { refreshDataDefinedProperty(); } );
113  connect( &mAtlasComposition, &QgsAtlasComposition::featureChanged, this, [this] { refreshDataDefinedProperty(); } );
114  //also, refreshing composition triggers a recalculation of data defined properties
115  connect( this, &QgsComposition::refreshItemsTriggered, this, [ = ] { refreshDataDefinedProperty(); } );
116  //toggling atlas or changing coverage layer requires data defined expressions to be reprepared
117  connect( &mAtlasComposition, &QgsAtlasComposition::toggled, this, [this] { prepareAllDataDefinedExpressions(); } );
118  connect( &mAtlasComposition, &QgsAtlasComposition::coverageLayerChanged, this, [this] { prepareAllDataDefinedExpressions(); } );
119 
120  setBackgroundBrush( QColor( 215, 215, 215 ) );
121  createDefaultPageStyleSymbol();
122 
123  addPaperItem();
124 
125  updateBounds();
126 
127  //add mouse selection handles to composition, and initially hide
128  mSelectionHandles = new QgsComposerMouseHandles( this );
129  addItem( mSelectionHandles );
130  mSelectionHandles->hide();
131  mSelectionHandles->setZValue( 500 );
132 
133  mPrintResolution = 300; //hardcoded default
134 
135  //load default composition settings
136  loadDefaults();
137  loadSettings();
138 
139  mItemsModel = new QgsComposerModel( this );
140 }
141 
142 
144 {
145  removePaperItems();
146  deleteAndRemoveMultiFrames();
147 
148  // make sure that all composer items are removed before
149  // this class is deconstructed - to avoid segfaults
150  // when composer items access in destructor composition that isn't valid anymore
151  QList<QGraphicsItem *> itemList = items();
152  qDeleteAll( itemList );
153 
154  //order is important here - we need to delete model last so that all items have already
155  //been deleted. Deleting the undo stack will also delete any items which have been
156  //removed from the scene, so this needs to be done before deleting the model
157  delete mUndoStack;
158 
159  delete mActiveItemCommand;
160  delete mActiveMultiFrameCommand;
161  delete mPageStyleSymbol;
162  delete mItemsModel;
163 }
164 
166 {
167  return mProject;
168 }
169 
170 void QgsComposition::setName( const QString &name )
171 {
172  mName = name;
173  emit nameChanged( name );
174 }
175 
176 void QgsComposition::loadDefaults()
177 {
178  QgsSettings settings;
179  mSnapGridResolution = settings.value( QStringLiteral( "Composer/defaultSnapGridResolution" ), 10.0 ).toDouble();
180  mSnapGridOffsetX = settings.value( QStringLiteral( "Composer/defaultSnapGridOffsetX" ), 0 ).toDouble();
181  mSnapGridOffsetY = settings.value( QStringLiteral( "Composer/defaultSnapGridOffsetY" ), 0 ).toDouble();
182  mSnapTolerance = settings.value( QStringLiteral( "Composer/defaultSnapTolerancePixels" ), 5 ).toInt();
183 }
184 
186 {
187  setSceneRect( compositionBounds( false, 0.05 ) );
188 }
189 
191 {
192  emit refreshItemsTriggered();
193 }
194 
196 {
198  if ( item )
199  {
200  item->setSelected( true );
201  emit selectedItemChanged( item );
202  }
203 }
204 
206 {
207  //we can't use QGraphicsScene::clearSelection, as that emits no signals
208  //and we don't know which items are being deselected
209  //accordingly, we can't inform the composition model of selection changes
210  //instead, do the clear selection manually...
211  QList<QGraphicsItem *> selectedItemList = selectedItems();
212  QList<QGraphicsItem *>::iterator itemIter = selectedItemList.begin();
213 
214  for ( ; itemIter != selectedItemList.end(); ++itemIter )
215  {
216  QgsComposerItem *composerItem = dynamic_cast<QgsComposerItem *>( *itemIter );
217  if ( composerItem )
218  {
219  composerItem->setSelected( false );
220  }
221  }
222  emit selectedItemChanged( nullptr );
223 }
224 
226 {
228  const QgsExpressionContext *evalContext = context ? context : &scopedContext;
229 
230 
231  //updates data defined properties and redraws composition to match
232  if ( property == QgsComposerObject::NumPages || property == QgsComposerObject::AllProperties )
233  {
234  setNumPages( numPages() );
235  }
236  if ( property == QgsComposerObject::PaperWidth || property == QgsComposerObject::PaperHeight ||
239  {
240  refreshPageSize( evalContext );
241  }
242 }
243 
244 QRectF QgsComposition::compositionBounds( bool ignorePages, double margin ) const
245 {
246  //start with an empty rectangle
247  QRectF bounds;
248 
249  //add all QgsComposerItems and QgsPaperItems which are in the composition
250  QList<QGraphicsItem *> itemList = items();
251  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
252  for ( ; itemIt != itemList.end(); ++itemIt )
253  {
254  const QgsComposerItem *composerItem = dynamic_cast<const QgsComposerItem *>( *itemIt );
255  const QgsPaperItem *paperItem = dynamic_cast<const QgsPaperItem *>( *itemIt );
256  if ( ( composerItem && ( !paperItem || !ignorePages ) ) )
257  {
258  //expand bounds with current item's bounds
259  if ( bounds.isValid() )
260  bounds = bounds.united( ( *itemIt )->sceneBoundingRect() );
261  else
262  bounds = ( *itemIt )->sceneBoundingRect();
263  }
264  }
265 
266  if ( bounds.isValid() && margin > 0.0 )
267  {
268  //finally, expand bounds out by specified margin of page size
269  bounds.adjust( -mPageWidth * margin, -mPageWidth * margin, mPageWidth * margin, mPageWidth * margin );
270  }
271 
272  return bounds;
273 }
274 
275 QRectF QgsComposition::pageItemBounds( int pageNumber, bool visibleOnly ) const
276 {
277  //start with an empty rectangle
278  QRectF bounds;
279 
280  //add all QgsComposerItems on page
281  QList<QGraphicsItem *> itemList = items();
282  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
283  for ( ; itemIt != itemList.end(); ++itemIt )
284  {
285  const QgsComposerItem *composerItem = dynamic_cast<const QgsComposerItem *>( *itemIt );
286  const QgsPaperItem *paperItem = dynamic_cast<const QgsPaperItem *>( *itemIt );
287  if ( composerItem && !paperItem && itemPageNumber( composerItem ) == pageNumber )
288  {
289  if ( visibleOnly && !composerItem->isVisible() )
290  continue;
291 
292  //expand bounds with current item's bounds
293  if ( bounds.isValid() )
294  bounds = bounds.united( ( *itemIt )->sceneBoundingRect() );
295  else
296  bounds = ( *itemIt )->sceneBoundingRect();
297  }
298  }
299 
300  return bounds;
301 }
302 
303 void QgsComposition::setPaperSize( const double width, const double height, bool keepRelativeItemPosition )
304 {
305  if ( qgsDoubleNear( width, mPageWidth ) && qgsDoubleNear( height, mPageHeight ) )
306  {
307  return;
308  }
309 
310  if ( keepRelativeItemPosition )
311  {
312  //update item positions
313  QList<QGraphicsItem *> itemList = items();
314  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
315  for ( ; itemIt != itemList.end(); ++itemIt )
316  {
317  QgsComposerItem *composerItem = dynamic_cast<QgsComposerItem *>( *itemIt );
318  if ( composerItem )
319  {
320  composerItem->updatePagePos( width, height );
321  }
322  }
323  }
324 
325  //update guide positions and size
326  QList< QGraphicsLineItem * > *guides = snapLines();
327  QList< QGraphicsLineItem * >::iterator guideIt = guides->begin();
328  double totalHeight = ( height + spaceBetweenPages() ) * ( numPages() - 1 ) + height;
329  for ( ; guideIt != guides->end(); ++guideIt )
330  {
331  QLineF line = ( *guideIt )->line();
332  if ( qgsDoubleNear( line.dx(), 0. ) )
333  {
334  //vertical line, change height of line
335  ( *guideIt )->setLine( line.x1(), 0, line.x1(), totalHeight );
336  }
337  else
338  {
339  //horizontal line
340  if ( keepRelativeItemPosition )
341  {
342  //move to new vertical position and change width of line
343  QPointF curPagePos = positionOnPage( line.p1() );
344  int curPage = pageNumberForPoint( line.p1() ) - 1;
345  double newY = curPage * ( height + spaceBetweenPages() ) + curPagePos.y();
346  ( *guideIt )->setLine( 0, newY, width, newY );
347  }
348  else
349  {
350  //just resize guide to new page size
351  ( *guideIt )->setLine( 0, line.y1(), width, line.y1() );
352  }
353  }
354  }
355 
356  mPageWidth = width;
357  mPageHeight = height;
358  double currentY = 0;
359  for ( int i = 0; i < mPages.size(); ++i )
360  {
361  mPages.at( i )->setSceneRect( QRectF( 0, currentY, width, height ) );
362  currentY += ( height + mSpaceBetweenPages );
363  }
364  mProject->setDirty( true );
365  updateBounds();
366  emit paperSizeChanged();
367 }
368 
370 {
371  return mPageHeight;
372 }
373 
375 {
376  return mPageWidth;
377 }
378 
379 void QgsComposition::resizePageToContents( double marginTop, double marginRight, double marginBottom, double marginLeft )
380 {
381  //calculate current bounds
382  QRectF bounds = compositionBounds( true, 0.0 );
383 
384  setNumPages( 1 );
385  double newWidth = bounds.width() + marginLeft + marginRight;
386  double newHeight = bounds.height() + marginTop + marginBottom;
387  setPaperSize( newWidth, newHeight, false );
388 
389  //also move all items so that top-left of bounds is at marginLeft, marginTop
390  double diffX = marginLeft - bounds.left();
391  double diffY = marginTop - bounds.top();
392 
393  QList<QGraphicsItem *> itemList = items();
394  Q_FOREACH ( QGraphicsItem *item, itemList )
395  {
396  QgsComposerItem *composerItem = dynamic_cast<QgsComposerItem *>( item );
397  if ( composerItem )
398  {
399  const QgsPaperItem *paperItem = dynamic_cast<const QgsPaperItem *>( item );
400 
401  if ( !paperItem )
402  composerItem->move( diffX, diffY );
403  }
404  }
405 
406  //also move guides
407  Q_FOREACH ( QGraphicsLineItem *guide, mSnapLines )
408  {
409  QLineF line = guide->line();
410  if ( qgsDoubleNear( line.dx(), 0.0 ) )
411  {
412  //vertical line
413  guide->setLine( line.x1() + diffX, 0, line.x1() + diffX, newHeight );
414  }
415  else
416  {
417  //horizontal line
418  guide->setLine( 0, line.y1() + diffY, newWidth, line.y1() + diffY );
419  }
420  }
421 }
422 
423 void QgsComposition::setResizeToContentsMargins( double marginTop, double marginRight, double marginBottom, double marginLeft )
424 {
425  mResizeToContentsMarginTop = marginTop;
426  mResizeToContentsMarginRight = marginRight;
427  mResizeToContentsMarginBottom = marginBottom;
428  mResizeToContentsMarginLeft = marginLeft;
429 }
430 
431 void QgsComposition::resizeToContentsMargins( double &marginTop, double &marginRight, double &marginBottom, double &marginLeft ) const
432 {
433  marginTop = mResizeToContentsMarginTop;
434  marginRight = mResizeToContentsMarginRight;
435  marginBottom = mResizeToContentsMarginBottom;
436  marginLeft = mResizeToContentsMarginLeft;
437 }
438 
440 {
441  int currentPages = numPages();
442  int desiredPages = pages;
443 
444  //data defined num pages set?
446  desiredPages = mDataDefinedProperties.valueAsInt( QgsComposerObject::NumPages, context, desiredPages );
447 
448  int diff = desiredPages - currentPages;
449  if ( diff >= 0 )
450  {
451  for ( int i = 0; i < diff; ++i )
452  {
453  addPaperItem();
454  }
455  }
456  else
457  {
458  diff = -diff;
459  for ( int i = 0; i < diff; ++i )
460  {
461  delete mPages.last();
462  mPages.removeLast();
463  }
464  }
465 
466  //update vertical guide height
467  QList< QGraphicsLineItem * > *guides = snapLines();
468  QList< QGraphicsLineItem * >::iterator guideIt = guides->begin();
469  double totalHeight = ( mPageHeight + spaceBetweenPages() ) * ( pages - 1 ) + mPageHeight;
470  for ( ; guideIt != guides->end(); ++guideIt )
471  {
472  QLineF line = ( *guideIt )->line();
473  if ( qgsDoubleNear( line.dx(), 0.0 ) )
474  {
475  //vertical line, change height of line
476  ( *guideIt )->setLine( line.x1(), 0, line.x1(), totalHeight );
477  }
478  }
479 
480  mProject->setDirty( true );
481  updateBounds();
482 
483  emit nPagesChanged();
484 }
485 
487 {
488  return mPages.size();
489 }
490 
491 bool QgsComposition::pageIsEmpty( const int page ) const
492 {
493  //get all items on page
494  QList<QgsComposerItem *> items;
495  //composerItemsOnPage uses 0-based page numbering
496  composerItemsOnPage( items, page - 1 );
497 
498  //loop through and check for non-paper items
499  QList<QgsComposerItem *>::const_iterator itemIt = items.constBegin();
500  for ( ; itemIt != items.constEnd(); ++itemIt )
501  {
502  //is item a paper item?
503  QgsPaperItem *paper = dynamic_cast<QgsPaperItem *>( *itemIt );
504  if ( !paper )
505  {
506  //item is not a paper item, so we have other items on the page
507  return false;
508  }
509  }
510  //no non-paper items
511  return true;
512 }
513 
514 bool QgsComposition::shouldExportPage( const int page ) const
515 {
516  if ( page > numPages() || page < 1 )
517  {
518  //page number out of range
519  return false;
520  }
521 
522  //check all frame items on page
523  QList<QgsComposerFrame *> frames;
524  //composerItemsOnPage uses 0 based page numbering
525  composerItemsOnPage( frames, page - 1 );
526  QList<QgsComposerFrame *>::const_iterator frameIt = frames.constBegin();
527  for ( ; frameIt != frames.constEnd(); ++frameIt )
528  {
529  if ( ( *frameIt )->hidePageIfEmpty() && ( *frameIt )->isEmpty() )
530  {
531  //frame is set to hide page if empty, and frame is empty, so we don't want to export this page
532  return false;
533  }
534  }
535  return true;
536 }
537 
539 {
540  delete mPageStyleSymbol;
541  mPageStyleSymbol = static_cast<QgsFillSymbol *>( symbol->clone() );
542  mProject->setDirty( true );
543 }
544 
545 void QgsComposition::createDefaultPageStyleSymbol()
546 {
547  delete mPageStyleSymbol;
548  QgsStringMap properties;
549  properties.insert( QStringLiteral( "color" ), QStringLiteral( "white" ) );
550  properties.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) );
551  properties.insert( QStringLiteral( "style_border" ), QStringLiteral( "no" ) );
552  properties.insert( QStringLiteral( "joinstyle" ), QStringLiteral( "miter" ) );
553  mPageStyleSymbol = QgsFillSymbol::createSimple( properties );
554 }
555 
556 QPointF QgsComposition::positionOnPage( QPointF position ) const
557 {
558  double y;
559  if ( position.y() > ( mPages.size() - 1 ) * ( paperHeight() + spaceBetweenPages() ) )
560  {
561  //y coordinate is greater then the end of the last page, so return distance between
562  //top of last page and y coordinate
563  y = position.y() - ( mPages.size() - 1 ) * ( paperHeight() + spaceBetweenPages() );
564  }
565  else
566  {
567  //y coordinate is less then the end of the last page
568  y = fmod( position.y(), ( paperHeight() + spaceBetweenPages() ) );
569  }
570  return QPointF( position.x(), y );
571 }
572 
573 int QgsComposition::pageNumberForPoint( QPointF position ) const
574 {
575  int pageNumber = qFloor( position.y() / ( paperHeight() + spaceBetweenPages() ) ) + 1;
576  pageNumber = pageNumber < 1 ? 1 : pageNumber;
577  pageNumber = pageNumber > mPages.size() ? mPages.size() : pageNumber;
578  return pageNumber;
579 }
580 
581 void QgsComposition::setStatusMessage( const QString &message )
582 {
583  emit statusMsgChanged( message );
584 }
585 
586 QgsComposerItem *QgsComposition::composerItemAt( QPointF position, bool ignoreLocked ) const
587 {
588  return composerItemAt( position, nullptr, ignoreLocked );
589 }
590 
591 QgsComposerItem *QgsComposition::composerItemAt( QPointF position, const QgsComposerItem *belowItem, const bool ignoreLocked ) const
592 {
593  //get a list of items which intersect the specified position, in descending z order
594  QList<QGraphicsItem *> itemList;
595  itemList = items( position, Qt::IntersectsItemShape, Qt::DescendingOrder );
596  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
597 
598  bool foundBelowItem = false;
599  for ( ; itemIt != itemList.end(); ++itemIt )
600  {
601  QgsComposerItem *composerItem = dynamic_cast<QgsComposerItem *>( *itemIt );
602  QgsPaperItem *paperItem = dynamic_cast<QgsPaperItem *>( *itemIt );
603  if ( composerItem && !paperItem )
604  {
605  // If we are not checking for a an item below a specified item, or if we've
606  // already found that item, then we've found our target
607  if ( ( ! belowItem || foundBelowItem ) && ( !ignoreLocked || !composerItem->positionLock() ) )
608  {
609  return composerItem;
610  }
611  else
612  {
613  if ( composerItem == belowItem )
614  {
615  //Target item is next in list
616  foundBelowItem = true;
617  }
618  }
619  }
620  }
621  return nullptr;
622 }
623 
624 int QgsComposition::pageNumberAt( QPointF position ) const
625 {
626  return position.y() / ( paperHeight() + spaceBetweenPages() );
627 }
628 
630 {
631  return pageNumberAt( QPointF( item->pos().x(), item->pos().y() ) );
632 }
633 
634 QList<QgsComposerItem *> QgsComposition::selectedComposerItems( const bool includeLockedItems )
635 {
636  QList<QgsComposerItem *> composerItemList;
637 
638  QList<QGraphicsItem *> graphicsItemList = selectedItems();
639  QList<QGraphicsItem *>::iterator itemIter = graphicsItemList.begin();
640 
641  for ( ; itemIter != graphicsItemList.end(); ++itemIter )
642  {
643  QgsComposerItem *composerItem = dynamic_cast<QgsComposerItem *>( *itemIter );
644  if ( composerItem && ( includeLockedItems || !composerItem->positionLock() ) )
645  {
646  composerItemList.push_back( composerItem );
647  }
648  }
649 
650  return composerItemList;
651 }
652 
653 QList<const QgsComposerMap *> QgsComposition::composerMapItems() const
654 {
655  QList<const QgsComposerMap *> resultList;
656 
657  QList<QGraphicsItem *> itemList = items();
658  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
659  for ( ; itemIt != itemList.end(); ++itemIt )
660  {
661  const QgsComposerMap *composerMap = dynamic_cast<const QgsComposerMap *>( *itemIt );
662  if ( composerMap )
663  {
664  resultList.push_back( composerMap );
665  }
666  }
667 
668  return resultList;
669 }
670 
672 {
673  QList<QGraphicsItem *> itemList = items();
674  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
675  for ( ; itemIt != itemList.end(); ++itemIt )
676  {
677  const QgsComposerMap *composerMap = dynamic_cast<const QgsComposerMap *>( *itemIt );
678  if ( composerMap )
679  {
680  if ( composerMap->id() == id )
681  {
682  return composerMap;
683  }
684  }
685  }
686  return nullptr;
687 }
688 
689 const QgsComposerItem *QgsComposition::getComposerItemById( const QString &id ) const
690 {
691  QList<QGraphicsItem *> itemList = items();
692  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
693  for ( ; itemIt != itemList.end(); ++itemIt )
694  {
695  const QgsComposerItem *mypItem = dynamic_cast<const QgsComposerItem *>( *itemIt );
696  if ( mypItem )
697  {
698  if ( mypItem->id() == id )
699  {
700  return mypItem;
701  }
702  }
703  }
704  return nullptr;
705 }
706 
707 #if 0
708 const QgsComposerItem *QgsComposition::getComposerItemByUuid( QString uuid, bool inAllComposers ) const
709 {
710  //This does not work since it seems impossible to get the QgisApp::instance() from here... Is there a workaround ?
711  QSet<QgsComposer *> composers = QSet<QgsComposer *>();
712 
713  if ( inAllComposers )
714  {
715  composers = QgisApp::instance()->printComposers();
716  }
717  else
718  {
719  composers.insert( this )
720  }
721 
722  QSet<QgsComposer *>::const_iterator it = composers.constBegin();
723  for ( ; it != composers.constEnd(); ++it )
724  {
725  QList<QGraphicsItem *> itemList = ( *it )->items();
726  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
727  for ( ; itemIt != itemList.end(); ++itemIt )
728  {
729  const QgsComposerItem *mypItem = dynamic_cast<const QgsComposerItem *>( *itemIt );
730  if ( mypItem )
731  {
732  if ( mypItem->uuid() == uuid )
733  {
734  return mypItem;
735  }
736  }
737  }
738  }
739 
740  return 0;
741 }
742 #endif
743 
744 const QgsComposerItem *QgsComposition::getComposerItemByUuid( const QString &uuid ) const
745 {
746  QList<QGraphicsItem *> itemList = items();
747  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
748  for ( ; itemIt != itemList.end(); ++itemIt )
749  {
750  const QgsComposerItem *mypItem = dynamic_cast<const QgsComposerItem *>( *itemIt );
751  if ( mypItem )
752  {
753  if ( mypItem->uuid() == uuid )
754  {
755  return mypItem;
756  }
757  }
758  }
759 
760  return nullptr;
761 }
762 
764 {
765  mPrintResolution = dpi;
766  emit printResolutionChanged();
767  mProject->setDirty( true );
768 }
769 
771 {
772  // prefer explicitly set reference map
773  if ( QgsComposerMap *map = dynamic_cast< QgsComposerMap * >( const_cast< QgsComposerItem * >( getComposerItemByUuid( mWorldFileMapId ) ) ) )
774  return map;
775 
776  // else try to find largest map
777  QList< const QgsComposerMap * > maps = composerMapItems();
778  const QgsComposerMap *largestMap = nullptr;
779  double largestMapArea = 0;
780  Q_FOREACH ( const QgsComposerMap *map, maps )
781  {
782  double area = map->rect().width() * map->rect().height();
783  if ( area > largestMapArea )
784  {
785  largestMapArea = area;
786  largestMap = map;
787  }
788  }
789  return const_cast< QgsComposerMap * >( largestMap );
790 }
791 
793 {
794  mWorldFileMapId = map ? map->uuid() : QString();
795  mProject->setDirty( true );
796 }
797 
798 void QgsComposition::setUseAdvancedEffects( const bool effectsEnabled )
799 {
800  mUseAdvancedEffects = effectsEnabled;
801 
802  //toggle effects for all composer items
803  QList<QGraphicsItem *> itemList = items();
804  QList<QGraphicsItem *>::const_iterator itemIt = itemList.constBegin();
805  for ( ; itemIt != itemList.constEnd(); ++itemIt )
806  {
807  QgsComposerItem *composerItem = dynamic_cast<QgsComposerItem *>( *itemIt );
808  if ( composerItem )
809  {
810  composerItem->setEffectsEnabled( effectsEnabled );
811  }
812  }
813 }
814 
815 bool QgsComposition::writeXml( QDomElement &composerElem, QDomDocument &doc )
816 {
817  if ( composerElem.isNull() )
818  {
819  return false;
820  }
821 
822  QDomElement compositionElem = doc.createElement( QStringLiteral( "Composition" ) );
823  compositionElem.setAttribute( QStringLiteral( "name" ), mName );
824  compositionElem.setAttribute( QStringLiteral( "paperWidth" ), QString::number( mPageWidth ) );
825  compositionElem.setAttribute( QStringLiteral( "paperHeight" ), QString::number( mPageHeight ) );
826  compositionElem.setAttribute( QStringLiteral( "numPages" ), mPages.size() );
827 
828  QgsReadWriteContext context;
829  context.setPathResolver( mProject->pathResolver() );
830 
831  QDomElement pageStyleElem = QgsSymbolLayerUtils::saveSymbol( QString(), mPageStyleSymbol, doc, context );
832  compositionElem.appendChild( pageStyleElem );
833 
834  //snapping
835  if ( mSnapToGrid )
836  {
837  compositionElem.setAttribute( QStringLiteral( "snapping" ), QStringLiteral( "1" ) );
838  }
839  else
840  {
841  compositionElem.setAttribute( QStringLiteral( "snapping" ), QStringLiteral( "0" ) );
842  }
843  if ( mGridVisible )
844  {
845  compositionElem.setAttribute( QStringLiteral( "gridVisible" ), QStringLiteral( "1" ) );
846  }
847  else
848  {
849  compositionElem.setAttribute( QStringLiteral( "gridVisible" ), QStringLiteral( "0" ) );
850  }
851  compositionElem.setAttribute( QStringLiteral( "snapGridResolution" ), QString::number( mSnapGridResolution ) );
852  compositionElem.setAttribute( QStringLiteral( "snapGridOffsetX" ), QString::number( mSnapGridOffsetX ) );
853  compositionElem.setAttribute( QStringLiteral( "snapGridOffsetY" ), QString::number( mSnapGridOffsetY ) );
854 
855  compositionElem.setAttribute( QStringLiteral( "showPages" ), mPagesVisible );
856 
857  //custom snap lines
858  QList< QGraphicsLineItem * >::const_iterator snapLineIt = mSnapLines.constBegin();
859  for ( ; snapLineIt != mSnapLines.constEnd(); ++snapLineIt )
860  {
861  QDomElement snapLineElem = doc.createElement( QStringLiteral( "SnapLine" ) );
862  QLineF line = ( *snapLineIt )->line();
863  snapLineElem.setAttribute( QStringLiteral( "x1" ), QString::number( line.x1() ) );
864  snapLineElem.setAttribute( QStringLiteral( "y1" ), QString::number( line.y1() ) );
865  snapLineElem.setAttribute( QStringLiteral( "x2" ), QString::number( line.x2() ) );
866  snapLineElem.setAttribute( QStringLiteral( "y2" ), QString::number( line.y2() ) );
867  compositionElem.appendChild( snapLineElem );
868  }
869 
870  compositionElem.setAttribute( QStringLiteral( "printResolution" ), mPrintResolution );
871  compositionElem.setAttribute( QStringLiteral( "printAsRaster" ), mPrintAsRaster );
872 
873  compositionElem.setAttribute( QStringLiteral( "generateWorldFile" ), mGenerateWorldFile ? 1 : 0 );
874  compositionElem.setAttribute( QStringLiteral( "worldFileMap" ), mWorldFileMapId );
875 
876  compositionElem.setAttribute( QStringLiteral( "alignmentSnap" ), mAlignmentSnap ? 1 : 0 );
877  compositionElem.setAttribute( QStringLiteral( "guidesVisible" ), mGuidesVisible ? 1 : 0 );
878  compositionElem.setAttribute( QStringLiteral( "smartGuides" ), mSmartGuides ? 1 : 0 );
879  compositionElem.setAttribute( QStringLiteral( "snapTolerancePixels" ), mSnapTolerance );
880 
881  compositionElem.setAttribute( QStringLiteral( "resizeToContentsMarginTop" ), mResizeToContentsMarginTop );
882  compositionElem.setAttribute( QStringLiteral( "resizeToContentsMarginRight" ), mResizeToContentsMarginRight );
883  compositionElem.setAttribute( QStringLiteral( "resizeToContentsMarginBottom" ), mResizeToContentsMarginBottom );
884  compositionElem.setAttribute( QStringLiteral( "resizeToContentsMarginLeft" ), mResizeToContentsMarginLeft );
885 
886  //save items except paper items and frame items (they are saved with the corresponding multiframe)
887  QList<QGraphicsItem *> itemList = items();
888  QList<QGraphicsItem *>::const_iterator itemIt = itemList.constBegin();
889  for ( ; itemIt != itemList.constEnd(); ++itemIt )
890  {
891  const QgsComposerItem *composerItem = dynamic_cast<const QgsComposerItem *>( *itemIt );
892  if ( composerItem )
893  {
894  if ( composerItem->type() != QgsComposerItem::ComposerPaper && composerItem->type() != QgsComposerItem::ComposerFrame )
895  {
896  composerItem->writeXml( compositionElem, doc );
897  }
898  }
899  }
900 
901  //save multiframes
902  QSet<QgsComposerMultiFrame *>::const_iterator multiFrameIt = mMultiFrames.constBegin();
903  for ( ; multiFrameIt != mMultiFrames.constEnd(); ++multiFrameIt )
904  {
905  ( *multiFrameIt )->writeXml( compositionElem, doc );
906  }
907 
908  //data defined properties
909  QDomElement ddPropsElement = doc.createElement( QStringLiteral( "dataDefinedProperties" ) );
910  mDataDefinedProperties.writeXml( ddPropsElement, QgsComposerObject::propertyDefinitions() );
911  compositionElem.appendChild( ddPropsElement );
912 
913  composerElem.appendChild( compositionElem );
914 
915  //custom properties
916  mCustomProperties.writeXml( compositionElem, doc );
917 
918  return true;
919 }
920 
921 bool QgsComposition::readXml( const QDomElement &compositionElem, const QDomDocument &doc )
922 {
923  Q_UNUSED( doc );
924  if ( compositionElem.isNull() )
925  {
926  return false;
927  }
928 
929  setName( compositionElem.attribute( QStringLiteral( "name" ) ) );
930 
931  //create pages
932  bool widthConversionOk, heightConversionOk;
933  mPageWidth = compositionElem.attribute( QStringLiteral( "paperWidth" ) ).toDouble( &widthConversionOk );
934  mPageHeight = compositionElem.attribute( QStringLiteral( "paperHeight" ) ).toDouble( &heightConversionOk );
935  emit paperSizeChanged();
936  int numPages = compositionElem.attribute( QStringLiteral( "numPages" ), QStringLiteral( "1" ) ).toInt();
937 
938  QgsReadWriteContext context;
939  context.setPathResolver( mProject->pathResolver() );
940 
941  QDomElement pageStyleSymbolElem = compositionElem.firstChildElement( QStringLiteral( "symbol" ) );
942  if ( !pageStyleSymbolElem.isNull() )
943  {
944  delete mPageStyleSymbol;
945  mPageStyleSymbol = QgsSymbolLayerUtils::loadSymbol<QgsFillSymbol>( pageStyleSymbolElem, context );
946  }
947 
948  if ( widthConversionOk && heightConversionOk )
949  {
950  removePaperItems();
951  for ( int i = 0; i < numPages; ++i )
952  {
953  addPaperItem();
954  }
955  }
956 
957  //snapping
958  mSnapToGrid = compositionElem.attribute( QStringLiteral( "snapping" ), QStringLiteral( "0" ) ).toInt() == 0 ? false : true;
959  mGridVisible = compositionElem.attribute( QStringLiteral( "gridVisible" ), QStringLiteral( "0" ) ).toInt() == 0 ? false : true;
960 
961  mSnapGridResolution = compositionElem.attribute( QStringLiteral( "snapGridResolution" ) ).toDouble();
962  mSnapGridOffsetX = compositionElem.attribute( QStringLiteral( "snapGridOffsetX" ) ).toDouble();
963  mSnapGridOffsetY = compositionElem.attribute( QStringLiteral( "snapGridOffsetY" ) ).toDouble();
964 
965  mAlignmentSnap = compositionElem.attribute( QStringLiteral( "alignmentSnap" ), QStringLiteral( "1" ) ).toInt() == 0 ? false : true;
966  mGuidesVisible = compositionElem.attribute( QStringLiteral( "guidesVisible" ), QStringLiteral( "1" ) ).toInt() == 0 ? false : true;
967  mSmartGuides = compositionElem.attribute( QStringLiteral( "smartGuides" ), QStringLiteral( "1" ) ).toInt() == 0 ? false : true;
968  mSnapTolerance = compositionElem.attribute( QStringLiteral( "snapTolerancePixels" ), QStringLiteral( "10" ) ).toInt();
969 
970  mResizeToContentsMarginTop = compositionElem.attribute( QStringLiteral( "resizeToContentsMarginTop" ), QStringLiteral( "0" ) ).toDouble();
971  mResizeToContentsMarginRight = compositionElem.attribute( QStringLiteral( "resizeToContentsMarginRight" ), QStringLiteral( "0" ) ).toDouble();
972  mResizeToContentsMarginBottom = compositionElem.attribute( QStringLiteral( "resizeToContentsMarginBottom" ), QStringLiteral( "0" ) ).toDouble();
973  mResizeToContentsMarginLeft = compositionElem.attribute( QStringLiteral( "resizeToContentsMarginLeft" ), QStringLiteral( "0" ) ).toDouble();
974 
975  //custom snap lines
976  QDomNodeList snapLineNodes = compositionElem.elementsByTagName( QStringLiteral( "SnapLine" ) );
977  for ( int i = 0; i < snapLineNodes.size(); ++i )
978  {
979  QDomElement snapLineElem = snapLineNodes.at( i ).toElement();
980  QGraphicsLineItem *snapItem = addSnapLine();
981  double x1 = snapLineElem.attribute( QStringLiteral( "x1" ) ).toDouble();
982  double y1 = snapLineElem.attribute( QStringLiteral( "y1" ) ).toDouble();
983  double x2 = snapLineElem.attribute( QStringLiteral( "x2" ) ).toDouble();
984  double y2 = snapLineElem.attribute( QStringLiteral( "y2" ) ).toDouble();
985  snapItem->setLine( x1, y1, x2, y2 );
986  }
987 
988  mPagesVisible = ( compositionElem.attribute( QStringLiteral( "showPages" ), QStringLiteral( "1" ) ) != QLatin1String( "0" ) );
989  mPrintAsRaster = compositionElem.attribute( QStringLiteral( "printAsRaster" ) ).toInt();
990  mPrintResolution = compositionElem.attribute( QStringLiteral( "printResolution" ), QStringLiteral( "300" ) ).toInt();
991 
992  mGenerateWorldFile = compositionElem.attribute( QStringLiteral( "generateWorldFile" ), QStringLiteral( "0" ) ).toInt() == 1;
993  mWorldFileMapId = compositionElem.attribute( QStringLiteral( "worldFileMap" ) );
994 
995  //data defined properties
996  //read old (pre 3.0) style data defined properties
997  QgsComposerUtils::readOldDataDefinedPropertyMap( compositionElem, mDataDefinedProperties );
998 
999  QDomNode propsNode = compositionElem.namedItem( QStringLiteral( "dataDefinedProperties" ) );
1000  if ( !propsNode.isNull() )
1001  {
1002  mDataDefinedProperties.readXml( propsNode.toElement(), QgsComposerObject::propertyDefinitions() );
1003  }
1004 
1005  //custom properties
1006  mCustomProperties.readXml( compositionElem );
1007 
1008  updatePaperItems();
1009 
1010  updateBounds();
1011 
1012  emit variablesChanged();
1013 
1014  return true;
1015 }
1016 
1017 bool QgsComposition::loadFromTemplate( const QDomDocument &doc, QMap<QString, QString> *substitutionMap, bool addUndoCommands, const bool clearComposition )
1018 {
1019  if ( clearComposition )
1020  {
1021  deleteAndRemoveMultiFrames();
1022 
1023  //delete all non paper items and emit itemRemoved signal
1024  QList<QGraphicsItem *> itemList = items();
1025  QList<QGraphicsItem *>::iterator itemIter = itemList.begin();
1026  for ( ; itemIter != itemList.end(); ++itemIter )
1027  {
1028  QgsComposerItem *cItem = dynamic_cast<QgsComposerItem *>( *itemIter );
1029  QgsPaperItem *pItem = dynamic_cast<QgsPaperItem *>( *itemIter );
1030  if ( cItem && !pItem )
1031  {
1032  removeItem( cItem );
1033  emit itemRemoved( cItem );
1034  delete cItem;
1035  }
1036  }
1037  mItemsModel->clear();
1038 
1039  removePaperItems();
1040  mUndoStack->clear();
1041  }
1042 
1043  QDomDocument importDoc;
1044  if ( substitutionMap )
1045  {
1046  QString xmlString = doc.toString();
1047  QMap<QString, QString>::const_iterator sIt = substitutionMap->constBegin();
1048  for ( ; sIt != substitutionMap->constEnd(); ++sIt )
1049  {
1050  xmlString = xmlString.replace( '[' + sIt.key() + ']', encodeStringForXml( sIt.value() ) );
1051  }
1052 
1053  QString errorMsg;
1054  int errorLine, errorColumn;
1055  if ( !importDoc.setContent( xmlString, &errorMsg, &errorLine, &errorColumn ) )
1056  {
1057  return false;
1058  }
1059  }
1060  else
1061  {
1062  importDoc = doc;
1063  }
1064 
1065  //read general settings
1066  QDomElement atlasElem;
1067  if ( clearComposition )
1068  {
1069  QDomElement compositionElem = importDoc.documentElement().firstChildElement( QStringLiteral( "Composition" ) );
1070  if ( compositionElem.isNull() )
1071  {
1072  return false;
1073  }
1074 
1075  bool ok = readXml( compositionElem, importDoc );
1076  if ( !ok )
1077  {
1078  return false;
1079  }
1080 
1081  // read atlas parameters - must be done before adding items
1082  atlasElem = importDoc.documentElement().firstChildElement( QStringLiteral( "Atlas" ) );
1083  atlasComposition().readXml( atlasElem, importDoc );
1084  }
1085 
1086  // remove all uuid attributes since we don't want duplicates UUIDS
1087  QDomNodeList composerItemsNodes = importDoc.elementsByTagName( QStringLiteral( "ComposerItem" ) );
1088  for ( int i = 0; i < composerItemsNodes.count(); ++i )
1089  {
1090  QDomNode composerItemNode = composerItemsNodes.at( i );
1091  if ( composerItemNode.isElement() )
1092  {
1093  composerItemNode.toElement().setAttribute( QStringLiteral( "templateUuid" ), composerItemNode.toElement().attribute( QStringLiteral( "uuid" ) ) );
1094  composerItemNode.toElement().removeAttribute( QStringLiteral( "uuid" ) );
1095  }
1096  }
1097 
1098  //addItemsFromXML
1099  addItemsFromXml( importDoc.documentElement(), importDoc, addUndoCommands, nullptr );
1100 
1101  return true;
1102 }
1103 
1104 QPointF QgsComposition::minPointFromXml( const QDomElement &elem ) const
1105 {
1106  double minX = std::numeric_limits<double>::max();
1107  double minY = std::numeric_limits<double>::max();
1108  QDomNodeList composerItemList = elem.elementsByTagName( QStringLiteral( "ComposerItem" ) );
1109  for ( int i = 0; i < composerItemList.size(); ++i )
1110  {
1111  QDomElement currentComposerItemElem = composerItemList.at( i ).toElement();
1112  double x, y;
1113  bool xOk, yOk;
1114  x = currentComposerItemElem.attribute( QStringLiteral( "x" ) ).toDouble( &xOk );
1115  y = currentComposerItemElem.attribute( QStringLiteral( "y" ) ).toDouble( &yOk );
1116  if ( !xOk || !yOk )
1117  {
1118  continue;
1119  }
1120  minX = qMin( minX, x );
1121  minY = qMin( minY, y );
1122  }
1123  if ( minX < std::numeric_limits<double>::max() )
1124  {
1125  return QPointF( minX, minY );
1126  }
1127  else
1128  {
1129  return QPointF( 0, 0 );
1130  }
1131 }
1132 
1133 void QgsComposition::addItemsFromXml( const QDomElement &elem, const QDomDocument &doc,
1134  bool addUndoCommands, QPointF *pos, bool pasteInPlace )
1135 {
1136  QPointF *pasteInPlacePt = nullptr;
1137 
1138  //if we are adding items to a composition which already contains items, we need to make sure
1139  //these items are placed at the top of the composition and that zValues are not duplicated
1140  //so, calculate an offset which needs to be added to the zValue of created items
1141  int zOrderOffset = mItemsModel->zOrderListSize();
1142 
1143  QPointF pasteShiftPos;
1144  QgsComposerItem *lastPastedItem = nullptr;
1145  if ( pos )
1146  {
1147  //If we are placing items relative to a certain point, then calculate how much we need
1148  //to shift the items by so that they are placed at this point
1149  //First, calculate the minimum position from the xml
1150  QPointF minItemPos = minPointFromXml( elem );
1151  //next, calculate how much each item needs to be shifted from its original position
1152  //so that it's placed at the correct relative position
1153  pasteShiftPos = *pos - minItemPos;
1154 
1155  //since we are pasting items, clear the existing selection
1156  setAllDeselected();
1157 
1158  if ( pasteInPlace )
1159  {
1160  pasteInPlacePt = new QPointF( 0, pageNumberAt( *pos ) * ( mPageHeight + mSpaceBetweenPages ) );
1161  }
1162  }
1163  QDomNodeList composerLabelList = elem.elementsByTagName( QStringLiteral( "ComposerLabel" ) );
1164  for ( int i = 0; i < composerLabelList.size(); ++i )
1165  {
1166  QDomElement currentComposerLabelElem = composerLabelList.at( i ).toElement();
1167  QgsComposerLabel *newLabel = new QgsComposerLabel( this );
1168  newLabel->readXml( currentComposerLabelElem, doc );
1169  if ( pos )
1170  {
1171  if ( pasteInPlacePt )
1172  {
1173  newLabel->setItemPosition( newLabel->pos().x(), fmod( newLabel->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
1174  newLabel->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
1175  }
1176  else
1177  {
1178  newLabel->move( pasteShiftPos.x(), pasteShiftPos.y() );
1179  }
1180  newLabel->setSelected( true );
1181  lastPastedItem = newLabel;
1182  }
1183  addComposerLabel( newLabel );
1184  newLabel->setZValue( newLabel->zValue() + zOrderOffset );
1185  if ( addUndoCommands )
1186  {
1187  pushAddRemoveCommand( newLabel, tr( "Label added" ) );
1188  }
1189  }
1190  // map
1191  QDomNodeList composerMapList = elem.elementsByTagName( QStringLiteral( "ComposerMap" ) );
1192  for ( int i = 0; i < composerMapList.size(); ++i )
1193  {
1194  QDomElement currentComposerMapElem = composerMapList.at( i ).toElement();
1195  QgsComposerMap *newMap = new QgsComposerMap( this );
1196 
1197  newMap->readXml( currentComposerMapElem, doc );
1198  newMap->assignFreeId();
1199 
1200  addComposerMap( newMap );
1201  newMap->setZValue( newMap->zValue() + zOrderOffset );
1202  if ( pos )
1203  {
1204  if ( pasteInPlace )
1205  {
1206  newMap->setItemPosition( newMap->pos().x(), fmod( newMap->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
1207  newMap->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
1208  }
1209  else
1210  {
1211  newMap->move( pasteShiftPos.x(), pasteShiftPos.y() );
1212  }
1213  newMap->setSelected( true );
1214  lastPastedItem = newMap;
1215  }
1216 
1217  if ( addUndoCommands )
1218  {
1219  pushAddRemoveCommand( newMap, tr( "Map added" ) );
1220  }
1221  }
1222  //now that all map items have been created, re-connect overview map signals
1223  QList<QgsComposerMap *> maps;
1224  composerItems( maps );
1225  for ( QList<QgsComposerMap *>::iterator mit = maps.begin(); mit != maps.end(); ++mit )
1226  {
1227  QgsComposerMap *map = ( *mit );
1228  if ( map )
1229  {
1230  QList<QgsComposerMapOverview * > overviews = map->overviews()->asList();
1231  QList<QgsComposerMapOverview * >::iterator overviewIt = overviews.begin();
1232  for ( ; overviewIt != overviews.end(); ++overviewIt )
1233  {
1234  ( *overviewIt )->connectSignals();
1235  }
1236  }
1237  }
1238 
1239  // arrow
1240  QDomNodeList composerArrowList = elem.elementsByTagName( QStringLiteral( "ComposerArrow" ) );
1241  for ( int i = 0; i < composerArrowList.size(); ++i )
1242  {
1243  QDomElement currentComposerArrowElem = composerArrowList.at( i ).toElement();
1244  QgsComposerArrow *newArrow = new QgsComposerArrow( this );
1245  newArrow->readXml( currentComposerArrowElem, doc );
1246  if ( pos )
1247  {
1248  if ( pasteInPlace )
1249  {
1250  newArrow->setItemPosition( newArrow->pos().x(), fmod( newArrow->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
1251  newArrow->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
1252  }
1253  else
1254  {
1255  newArrow->move( pasteShiftPos.x(), pasteShiftPos.y() );
1256  }
1257  newArrow->setSelected( true );
1258  lastPastedItem = newArrow;
1259  }
1260  addComposerArrow( newArrow );
1261  newArrow->setZValue( newArrow->zValue() + zOrderOffset );
1262  if ( addUndoCommands )
1263  {
1264  pushAddRemoveCommand( newArrow, tr( "Arrow added" ) );
1265  }
1266  }
1267  // scalebar
1268  QDomNodeList composerScaleBarList = elem.elementsByTagName( QStringLiteral( "ComposerScaleBar" ) );
1269  for ( int i = 0; i < composerScaleBarList.size(); ++i )
1270  {
1271  QDomElement currentComposerScaleBarElem = composerScaleBarList.at( i ).toElement();
1272  QgsComposerScaleBar *newScaleBar = new QgsComposerScaleBar( this );
1273  newScaleBar->readXml( currentComposerScaleBarElem, doc );
1274  if ( pos )
1275  {
1276  if ( pasteInPlace )
1277  {
1278  newScaleBar->setItemPosition( newScaleBar->pos().x(), fmod( newScaleBar->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
1279  newScaleBar->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
1280  }
1281  else
1282  {
1283  newScaleBar->move( pasteShiftPos.x(), pasteShiftPos.y() );
1284  }
1285  newScaleBar->setSelected( true );
1286  lastPastedItem = newScaleBar;
1287  }
1288  addComposerScaleBar( newScaleBar );
1289  newScaleBar->setZValue( newScaleBar->zValue() + zOrderOffset );
1290  if ( addUndoCommands )
1291  {
1292  pushAddRemoveCommand( newScaleBar, tr( "Scale bar added" ) );
1293  }
1294  }
1295  // shape
1296  QDomNodeList composerShapeList = elem.elementsByTagName( QStringLiteral( "ComposerShape" ) );
1297  for ( int i = 0; i < composerShapeList.size(); ++i )
1298  {
1299  QDomElement currentComposerShapeElem = composerShapeList.at( i ).toElement();
1300  QgsComposerShape *newShape = new QgsComposerShape( this );
1301  newShape->readXml( currentComposerShapeElem, doc );
1302  //new shapes should default to symbol v2
1303  newShape->setUseSymbol( true );
1304  if ( pos )
1305  {
1306  if ( pasteInPlace )
1307  {
1308  newShape->setItemPosition( newShape->pos().x(), fmod( newShape->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
1309  newShape->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
1310  }
1311  else
1312  {
1313  newShape->move( pasteShiftPos.x(), pasteShiftPos.y() );
1314  }
1315  newShape->setSelected( true );
1316  lastPastedItem = newShape;
1317  }
1318  addComposerShape( newShape );
1319  newShape->setZValue( newShape->zValue() + zOrderOffset );
1320  if ( addUndoCommands )
1321  {
1322  pushAddRemoveCommand( newShape, tr( "Shape added" ) );
1323  }
1324  }
1325 
1326  // polygon
1327  QDomNodeList composerPolygonList = elem.elementsByTagName( QStringLiteral( "ComposerPolygon" ) );
1328  for ( int i = 0; i < composerPolygonList.size(); ++i )
1329  {
1330  QDomElement currentComposerPolygonElem = composerPolygonList.at( i ).toElement();
1331  QgsComposerPolygon *newPolygon = new QgsComposerPolygon( this );
1332  newPolygon->readXml( currentComposerPolygonElem, doc );
1333 
1334  if ( pos )
1335  {
1336  if ( pasteInPlace )
1337  {
1338  newPolygon->setItemPosition( newPolygon->pos().x(), fmod( newPolygon->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
1339  newPolygon->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
1340  }
1341  else
1342  {
1343  newPolygon->move( pasteShiftPos.x(), pasteShiftPos.y() );
1344  }
1345  newPolygon->setSelected( true );
1346  lastPastedItem = newPolygon;
1347  }
1348 
1349  addComposerPolygon( newPolygon );
1350  newPolygon->setZValue( newPolygon->zValue() + zOrderOffset );
1351  if ( addUndoCommands )
1352  {
1353  pushAddRemoveCommand( newPolygon, tr( "Polygon added" ) );
1354  }
1355  }
1356 
1357  // polyline
1358  QDomNodeList addComposerPolylineList = elem.elementsByTagName( QStringLiteral( "ComposerPolyline" ) );
1359  for ( int i = 0; i < addComposerPolylineList.size(); ++i )
1360  {
1361  QDomElement currentComposerPolylineElem = addComposerPolylineList.at( i ).toElement();
1362  QgsComposerPolyline *newPolyline = new QgsComposerPolyline( this );
1363  newPolyline->readXml( currentComposerPolylineElem, doc );
1364 
1365  if ( pos )
1366  {
1367  if ( pasteInPlace )
1368  {
1369  newPolyline->setItemPosition( newPolyline->pos().x(), fmod( newPolyline->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
1370  newPolyline->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
1371  }
1372  else
1373  {
1374  newPolyline->move( pasteShiftPos.x(), pasteShiftPos.y() );
1375  }
1376  newPolyline->setSelected( true );
1377  lastPastedItem = newPolyline;
1378  }
1379 
1380  addComposerPolyline( newPolyline );
1381  newPolyline->setZValue( newPolyline->zValue() + zOrderOffset );
1382  if ( addUndoCommands )
1383  {
1384  pushAddRemoveCommand( newPolyline, tr( "Polyline added" ) );
1385  }
1386  }
1387 
1388  // picture
1389  QDomNodeList composerPictureList = elem.elementsByTagName( QStringLiteral( "ComposerPicture" ) );
1390  for ( int i = 0; i < composerPictureList.size(); ++i )
1391  {
1392  QDomElement currentComposerPictureElem = composerPictureList.at( i ).toElement();
1393  QgsComposerPicture *newPicture = new QgsComposerPicture( this );
1394  newPicture->readXml( currentComposerPictureElem, doc );
1395  if ( pos )
1396  {
1397  if ( pasteInPlace )
1398  {
1399  newPicture->setItemPosition( newPicture->pos().x(), fmod( newPicture->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
1400  newPicture->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
1401  }
1402  else
1403  {
1404  newPicture->move( pasteShiftPos.x(), pasteShiftPos.y() );
1405  }
1406  newPicture->setSelected( true );
1407  lastPastedItem = newPicture;
1408  }
1409  addComposerPicture( newPicture );
1410  newPicture->setZValue( newPicture->zValue() + zOrderOffset );
1411  if ( addUndoCommands )
1412  {
1413  pushAddRemoveCommand( newPicture, tr( "Picture added" ) );
1414  }
1415  }
1416  // legend
1417  QDomNodeList composerLegendList = elem.elementsByTagName( QStringLiteral( "ComposerLegend" ) );
1418  for ( int i = 0; i < composerLegendList.size(); ++i )
1419  {
1420  QDomElement currentComposerLegendElem = composerLegendList.at( i ).toElement();
1421  QgsComposerLegend *newLegend = new QgsComposerLegend( this );
1422  newLegend->readXml( currentComposerLegendElem, doc );
1423  if ( pos )
1424  {
1425  if ( pasteInPlace )
1426  {
1427  newLegend->setItemPosition( newLegend->pos().x(), fmod( newLegend->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
1428  newLegend->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
1429  }
1430  else
1431  {
1432  newLegend->move( pasteShiftPos.x(), pasteShiftPos.y() );
1433  }
1434  newLegend->setSelected( true );
1435  lastPastedItem = newLegend;
1436  }
1437  addComposerLegend( newLegend );
1438  newLegend->setZValue( newLegend->zValue() + zOrderOffset );
1439  if ( addUndoCommands )
1440  {
1441  pushAddRemoveCommand( newLegend, tr( "Legend added" ) );
1442  }
1443  }
1444 
1445  // html
1446  //TODO - fix this. pasting multiframe frame items has no effect
1447  QDomNodeList composerHtmlList = elem.elementsByTagName( QStringLiteral( "ComposerHtml" ) );
1448  for ( int i = 0; i < composerHtmlList.size(); ++i )
1449  {
1450  QDomElement currentHtmlElem = composerHtmlList.at( i ).toElement();
1451  QgsComposerHtml *newHtml = new QgsComposerHtml( this, false );
1452  newHtml->readXml( currentHtmlElem, doc );
1453  newHtml->setCreateUndoCommands( true );
1454  this->addMultiFrame( newHtml );
1455 
1456  //offset z values for frames
1457  //TODO - fix this after fixing html item paste
1458  /*for ( int frameIdx = 0; frameIdx < newHtml->frameCount(); ++frameIdx )
1459  {
1460  QgsComposerFrame * frame = newHtml->frame( frameIdx );
1461  frame->setZValue( frame->zValue() + zOrderOffset );
1462  }*/
1463  }
1464  QDomNodeList composerAttributeTableV2List = elem.elementsByTagName( QStringLiteral( "ComposerAttributeTableV2" ) );
1465  for ( int i = 0; i < composerAttributeTableV2List.size(); ++i )
1466  {
1467  QDomElement currentTableElem = composerAttributeTableV2List.at( i ).toElement();
1468  QgsComposerAttributeTableV2 *newTable = new QgsComposerAttributeTableV2( this, false );
1469  newTable->readXml( currentTableElem, doc );
1470  newTable->setCreateUndoCommands( true );
1471  this->addMultiFrame( newTable );
1472 
1473  //offset z values for frames
1474  //TODO - fix this after fixing html item paste
1475  /*for ( int frameIdx = 0; frameIdx < newHtml->frameCount(); ++frameIdx )
1476  {
1477  QgsComposerFrame * frame = newHtml->frame( frameIdx );
1478  frame->setZValue( frame->zValue() + zOrderOffset );
1479  }*/
1480  }
1481 
1482  // groups (must be last as it references uuids of above items)
1483  //TODO - pasted groups lose group properties, since the uuids of group items
1484  //changes
1485  QDomNodeList groupList = elem.elementsByTagName( QStringLiteral( "ComposerItemGroup" ) );
1486  for ( int i = 0; i < groupList.size(); ++i )
1487  {
1488  QDomElement groupElem = groupList.at( i ).toElement();
1489  QgsComposerItemGroup *newGroup = new QgsComposerItemGroup( this );
1490  newGroup->readXml( groupElem, doc );
1491  addItem( newGroup );
1492  if ( addUndoCommands )
1493  {
1494  pushAddRemoveCommand( newGroup, tr( "Group added" ) );
1495  }
1496  }
1497 
1498  //Since this function adds items grouped by type, and each item is added to end of
1499  //z order list in turn, it will now be inconsistent with the actual order of items in the scene.
1500  //Make sure z order list matches the actual order of items in the scene.
1501  mItemsModel->rebuildZList();
1502 
1503  if ( lastPastedItem )
1504  {
1505  emit selectedItemChanged( lastPastedItem );
1506  }
1507 
1508  delete pasteInPlacePt;
1509  pasteInPlacePt = nullptr;
1510 
1511 }
1512 
1514 {
1515  if ( !item )
1516  {
1517  return;
1518  }
1519 
1520  //model handles changes to z list
1521  mItemsModel->addItemAtTop( item );
1522 }
1523 
1525 {
1526  if ( !item )
1527  {
1528  return;
1529  }
1530 
1531  //model handles changes to z list
1532  mItemsModel->removeItem( item );
1533 }
1534 
1536 {
1537  QList<QgsComposerItem *> selectedItems = selectedComposerItems();
1538  QList<QgsComposerItem *>::iterator it = selectedItems.begin();
1539  bool itemsRaised = false;
1540  for ( ; it != selectedItems.end(); ++it )
1541  {
1542  itemsRaised = itemsRaised | raiseItem( *it );
1543  }
1544 
1545  if ( !itemsRaised )
1546  {
1547  //no change
1548  return;
1549  }
1550 
1551  //update all positions
1552  updateZValues();
1553  update();
1554 }
1555 
1557 {
1558  //model handles reordering items
1559  return mItemsModel->reorderItemUp( item );
1560 }
1561 
1563 {
1564  return mItemsModel->getComposerItemAbove( item );
1565 }
1566 
1568 {
1569  return mItemsModel->getComposerItemBelow( item );
1570 }
1571 
1573 {
1574  QgsComposerItem *previousSelectedItem = nullptr;
1575  QList<QgsComposerItem *> selectedItems = selectedComposerItems();
1576  if ( !selectedItems.isEmpty() )
1577  {
1578  previousSelectedItem = selectedItems.at( 0 );
1579  }
1580 
1581  if ( !previousSelectedItem )
1582  {
1583  return;
1584  }
1585 
1586  //select item with target z value
1587  QgsComposerItem *selectedItem = nullptr;
1588  switch ( direction )
1589  {
1591  selectedItem = getComposerItemBelow( previousSelectedItem );
1592  break;
1594  selectedItem = getComposerItemAbove( previousSelectedItem );
1595  break;
1596  }
1597 
1598  if ( !selectedItem )
1599  {
1600  return;
1601  }
1602 
1603  //ok, found a good target item
1604  setAllDeselected();
1605  selectedItem->setSelected( true );
1606  emit selectedItemChanged( selectedItem );
1607 }
1608 
1610 {
1611  QList<QgsComposerItem *> selectedItems = selectedComposerItems();
1612  QList<QgsComposerItem *>::iterator it = selectedItems.begin();
1613  bool itemsLowered = false;
1614  for ( ; it != selectedItems.end(); ++it )
1615  {
1616  itemsLowered = itemsLowered | lowerItem( *it );
1617  }
1618 
1619  if ( !itemsLowered )
1620  {
1621  //no change
1622  return;
1623  }
1624 
1625  //update all positions
1626  updateZValues();
1627  update();
1628 }
1629 
1631 {
1632  //model handles reordering items
1633  return mItemsModel->reorderItemDown( item );
1634 }
1635 
1637 {
1638  QList<QgsComposerItem *> selectedItems = selectedComposerItems();
1639  QList<QgsComposerItem *>::iterator it = selectedItems.begin();
1640  bool itemsRaised = false;
1641  for ( ; it != selectedItems.end(); ++it )
1642  {
1643  itemsRaised = itemsRaised | moveItemToTop( *it );
1644  }
1645 
1646  if ( !itemsRaised )
1647  {
1648  //no change
1649  return;
1650  }
1651 
1652  //update all positions
1653  updateZValues();
1654  update();
1655 }
1656 
1658 {
1659  //model handles reordering items
1660  return mItemsModel->reorderItemToTop( item );
1661 }
1662 
1664 {
1665  QList<QgsComposerItem *> selectedItems = selectedComposerItems();
1666  QList<QgsComposerItem *>::iterator it = selectedItems.begin();
1667  bool itemsLowered = false;
1668  for ( ; it != selectedItems.end(); ++it )
1669  {
1670  itemsLowered = itemsLowered | moveItemToBottom( *it );
1671  }
1672 
1673  if ( !itemsLowered )
1674  {
1675  //no change
1676  return;
1677  }
1678 
1679  //update all positions
1680  updateZValues();
1681  update();
1682 }
1683 
1685 {
1686  //model handles reordering items
1687  return mItemsModel->reorderItemToBottom( item );
1688 }
1689 
1691 {
1692  QList<QgsComposerItem *> selectedItems = selectedComposerItems();
1693  if ( selectedItems.size() < 2 )
1694  {
1695  return;
1696  }
1697 
1698  QRectF selectedItemBBox;
1699  if ( boundingRectOfSelectedItems( selectedItemBBox ) != 0 )
1700  {
1701  return;
1702  }
1703 
1704  double minXCoordinate = selectedItemBBox.left();
1705 
1706  //align items left to minimum x coordinate
1707  QUndoCommand *parentCommand = new QUndoCommand( tr( "Aligned items left" ) );
1708  QList<QgsComposerItem *>::iterator align_it = selectedItems.begin();
1709  for ( ; align_it != selectedItems.end(); ++align_it )
1710  {
1711  QgsComposerItemCommand *subcommand = new QgsComposerItemCommand( *align_it, QLatin1String( "" ), parentCommand );
1712  subcommand->savePreviousState();
1713  ( *align_it )->setPos( minXCoordinate, ( *align_it )->pos().y() );
1714  subcommand->saveAfterState();
1715  }
1716  mUndoStack->push( parentCommand );
1717  mProject->setDirty( true );
1718 }
1719 
1721 {
1722  QList<QgsComposerItem *> selectedItems = selectedComposerItems();
1723  if ( selectedItems.size() < 2 )
1724  {
1725  return;
1726  }
1727 
1728  QRectF selectedItemBBox;
1729  if ( boundingRectOfSelectedItems( selectedItemBBox ) != 0 )
1730  {
1731  return;
1732  }
1733 
1734  double averageXCoord = ( selectedItemBBox.left() + selectedItemBBox.right() ) / 2.0;
1735 
1736  //place items
1737  QUndoCommand *parentCommand = new QUndoCommand( tr( "Aligned items horizontal center" ) );
1738  QList<QgsComposerItem *>::iterator align_it = selectedItems.begin();
1739  for ( ; align_it != selectedItems.end(); ++align_it )
1740  {
1741  QgsComposerItemCommand *subcommand = new QgsComposerItemCommand( *align_it, QLatin1String( "" ), parentCommand );
1742  subcommand->savePreviousState();
1743  ( *align_it )->setPos( averageXCoord - ( *align_it )->rect().width() / 2.0, ( *align_it )->pos().y() );
1744  subcommand->saveAfterState();
1745  }
1746  mUndoStack->push( parentCommand );
1747  mProject->setDirty( true );
1748 }
1749 
1751 {
1752  QList<QgsComposerItem *> selectedItems = selectedComposerItems();
1753  if ( selectedItems.size() < 2 )
1754  {
1755  return;
1756  }
1757 
1758  QRectF selectedItemBBox;
1759  if ( boundingRectOfSelectedItems( selectedItemBBox ) != 0 )
1760  {
1761  return;
1762  }
1763 
1764  double maxXCoordinate = selectedItemBBox.right();
1765 
1766  //align items right to maximum x coordinate
1767  QUndoCommand *parentCommand = new QUndoCommand( tr( "Aligned items right" ) );
1768  QList<QgsComposerItem *>::iterator align_it = selectedItems.begin();
1769  for ( ; align_it != selectedItems.end(); ++align_it )
1770  {
1771  QgsComposerItemCommand *subcommand = new QgsComposerItemCommand( *align_it, QLatin1String( "" ), parentCommand );
1772  subcommand->savePreviousState();
1773  ( *align_it )->setPos( maxXCoordinate - ( *align_it )->rect().width(), ( *align_it )->pos().y() );
1774  subcommand->saveAfterState();
1775  }
1776  mUndoStack->push( parentCommand );
1777  mProject->setDirty( true );
1778 }
1779 
1781 {
1782  QList<QgsComposerItem *> selectedItems = selectedComposerItems();
1783  if ( selectedItems.size() < 2 )
1784  {
1785  return;
1786  }
1787 
1788  QRectF selectedItemBBox;
1789  if ( boundingRectOfSelectedItems( selectedItemBBox ) != 0 )
1790  {
1791  return;
1792  }
1793 
1794  double minYCoordinate = selectedItemBBox.top();
1795 
1796  QUndoCommand *parentCommand = new QUndoCommand( tr( "Aligned items top" ) );
1797  QList<QgsComposerItem *>::iterator align_it = selectedItems.begin();
1798  for ( ; align_it != selectedItems.end(); ++align_it )
1799  {
1800  QgsComposerItemCommand *subcommand = new QgsComposerItemCommand( *align_it, QLatin1String( "" ), parentCommand );
1801  subcommand->savePreviousState();
1802  ( *align_it )->setPos( ( *align_it )->pos().x(), minYCoordinate );
1803  subcommand->saveAfterState();
1804  }
1805  mUndoStack->push( parentCommand );
1806  mProject->setDirty( true );
1807 }
1808 
1810 {
1811  QList<QgsComposerItem *> selectedItems = selectedComposerItems();
1812  if ( selectedItems.size() < 2 )
1813  {
1814  return;
1815  }
1816 
1817  QRectF selectedItemBBox;
1818  if ( boundingRectOfSelectedItems( selectedItemBBox ) != 0 )
1819  {
1820  return;
1821  }
1822 
1823  double averageYCoord = ( selectedItemBBox.top() + selectedItemBBox.bottom() ) / 2.0;
1824  QUndoCommand *parentCommand = new QUndoCommand( tr( "Aligned items vertical center" ) );
1825  QList<QgsComposerItem *>::iterator align_it = selectedItems.begin();
1826  for ( ; align_it != selectedItems.end(); ++align_it )
1827  {
1828  QgsComposerItemCommand *subcommand = new QgsComposerItemCommand( *align_it, QLatin1String( "" ), parentCommand );
1829  subcommand->savePreviousState();
1830  ( *align_it )->setPos( ( *align_it )->pos().x(), averageYCoord - ( *align_it )->rect().height() / 2 );
1831  subcommand->saveAfterState();
1832  }
1833  mUndoStack->push( parentCommand );
1834  mProject->setDirty( true );
1835 }
1836 
1838 {
1839  QList<QgsComposerItem *> selectedItems = selectedComposerItems();
1840  if ( selectedItems.size() < 2 )
1841  {
1842  return;
1843  }
1844 
1845  QRectF selectedItemBBox;
1846  if ( boundingRectOfSelectedItems( selectedItemBBox ) != 0 )
1847  {
1848  return;
1849  }
1850 
1851  double maxYCoord = selectedItemBBox.bottom();
1852  QUndoCommand *parentCommand = new QUndoCommand( tr( "Aligned items bottom" ) );
1853  QList<QgsComposerItem *>::iterator align_it = selectedItems.begin();
1854  for ( ; align_it != selectedItems.end(); ++align_it )
1855  {
1856  QgsComposerItemCommand *subcommand = new QgsComposerItemCommand( *align_it, QLatin1String( "" ), parentCommand );
1857  subcommand->savePreviousState();
1858  ( *align_it )->setPos( ( *align_it )->pos().x(), maxYCoord - ( *align_it )->rect().height() );
1859  subcommand->saveAfterState();
1860  }
1861  mUndoStack->push( parentCommand );
1862  mProject->setDirty( true );
1863 }
1864 
1866 {
1867  QUndoCommand *parentCommand = new QUndoCommand( tr( "Items locked" ) );
1868  QList<QgsComposerItem *> selectionList = selectedComposerItems();
1869  QList<QgsComposerItem *>::iterator itemIter = selectionList.begin();
1870  for ( ; itemIter != selectionList.end(); ++itemIter )
1871  {
1872  QgsComposerItemCommand *subcommand = new QgsComposerItemCommand( *itemIter, QLatin1String( "" ), parentCommand );
1873  subcommand->savePreviousState();
1874  ( *itemIter )->setPositionLock( true );
1875  subcommand->saveAfterState();
1876  }
1877 
1878  setAllDeselected();
1879  mUndoStack->push( parentCommand );
1880  mProject->setDirty( true );
1881 }
1882 
1884 {
1885  //unlock all items in composer
1886 
1887  QUndoCommand *parentCommand = new QUndoCommand( tr( "Items unlocked" ) );
1888 
1889  //first, clear the selection
1890  setAllDeselected();
1891 
1892  QList<QGraphicsItem *> itemList = items();
1893  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
1894  for ( ; itemIt != itemList.end(); ++itemIt )
1895  {
1896  QgsComposerItem *mypItem = dynamic_cast<QgsComposerItem *>( *itemIt );
1897  if ( mypItem && mypItem->positionLock() )
1898  {
1899  QgsComposerItemCommand *subcommand = new QgsComposerItemCommand( mypItem, QLatin1String( "" ), parentCommand );
1900  subcommand->savePreviousState();
1901  mypItem->setPositionLock( false );
1902  //select unlocked items, same behavior as illustrator
1903  mypItem->setSelected( true );
1904  emit selectedItemChanged( mypItem );
1905  subcommand->saveAfterState();
1906  }
1907  }
1908  mUndoStack->push( parentCommand );
1909  mProject->setDirty( true );
1910 }
1911 
1912 QgsComposerItemGroup *QgsComposition::groupItems( QList<QgsComposerItem *> items )
1913 {
1914  if ( items.size() < 2 )
1915  {
1916  //not enough items for a group
1917  return nullptr;
1918  }
1919 
1920  QgsComposerItemGroup *itemGroup = new QgsComposerItemGroup( this );
1921  QgsDebugMsg( QString( "itemgroup created with %1 items (%2 to be added)" ) .arg( itemGroup->items().size() ).arg( items.size() ) );
1922 
1923  QList<QgsComposerItem *>::iterator itemIter = items.begin();
1924  for ( ; itemIter != items.end(); ++itemIter )
1925  {
1926  itemGroup->addItem( *itemIter );
1927  QgsDebugMsg( QString( "itemgroup now has %1" )
1928  .arg( itemGroup->items().size() ) );
1929  }
1930 
1931  addItem( itemGroup );
1932 
1933  QgsGroupUngroupItemsCommand *c = new QgsGroupUngroupItemsCommand( QgsGroupUngroupItemsCommand::Grouped, itemGroup, this, tr( "Items grouped" ) );
1936 
1937  undoStack()->push( c );
1938  mProject->setDirty( true );
1939  //QgsDebugMsg( QString( "itemgroup after pushAddRemove has %1" ) .arg( itemGroup->items().size() ) );
1940 
1941  emit composerItemGroupAdded( itemGroup );
1942 
1943  return itemGroup;
1944 }
1945 
1946 QList<QgsComposerItem *> QgsComposition::ungroupItems( QgsComposerItemGroup *group )
1947 {
1948  QList<QgsComposerItem *> ungroupedItems;
1949  if ( !group )
1950  {
1951  return ungroupedItems;
1952  }
1953 
1954  // group ownership transferred to QgsGroupUngroupItemsCommand
1955  // Call this before removing group items so it can keep note
1956  // of contents
1960 
1961  undoStack()->push( c );
1962  mProject->setDirty( true );
1963 
1964 
1965  QSet<QgsComposerItem *> groupedItems = group->items();
1966  QSet<QgsComposerItem *>::iterator itemIt = groupedItems.begin();
1967  for ( ; itemIt != groupedItems.end(); ++itemIt )
1968  {
1969  ungroupedItems << ( *itemIt );
1970  }
1971 
1972  group->removeItems();
1973 
1974  // note: emits itemRemoved
1975  removeComposerItem( group, false, false );
1976 
1977  return ungroupedItems;
1978 }
1979 
1980 void QgsComposition::updateZValues( const bool addUndoCommands )
1981 {
1982  int counter = mItemsModel->zOrderListSize();
1983  QList<QgsComposerItem *>::const_iterator it = mItemsModel->zOrderList()->constBegin();
1984  QgsComposerItem *currentItem = nullptr;
1985 
1986  QUndoCommand *parentCommand = nullptr;
1987  if ( addUndoCommands )
1988  {
1989  parentCommand = new QUndoCommand( tr( "Item z-order changed" ) );
1990  }
1991  for ( ; it != mItemsModel->zOrderList()->constEnd(); ++it )
1992  {
1993  currentItem = *it;
1994  if ( currentItem )
1995  {
1996  QgsComposerItemCommand *subcommand = nullptr;
1997  if ( addUndoCommands )
1998  {
1999  subcommand = new QgsComposerItemCommand( *it, QLatin1String( "" ), parentCommand );
2000  subcommand->savePreviousState();
2001  }
2002  currentItem->setZValue( counter );
2003  if ( addUndoCommands )
2004  {
2005  subcommand->saveAfterState();
2006  }
2007  }
2008  --counter;
2009  }
2010  if ( addUndoCommands )
2011  {
2012  mUndoStack->push( parentCommand );
2013  mProject->setDirty( true );
2014  }
2015 }
2016 
2018 {
2019  //model handles changes to item z order list
2020  mItemsModel->rebuildZList();
2021 
2022  //Finally, rebuild the zValue of all items to remove any duplicate zValues and make sure there's
2023  //no missing zValues.
2024  updateZValues( false );
2025 }
2026 
2027 QPointF QgsComposition::snapPointToGrid( QPointF scenePoint ) const
2028 {
2029  if ( !mSnapToGrid || mSnapGridResolution <= 0 || !graphicsView() )
2030  {
2031  return scenePoint;
2032  }
2033 
2034  //y offset to current page
2035  int pageNr = static_cast< int >( scenePoint.y() / ( mPageHeight + mSpaceBetweenPages ) );
2036  double yOffset = pageNr * ( mPageHeight + mSpaceBetweenPages );
2037  double yPage = scenePoint.y() - yOffset; //y-coordinate relative to current page
2038 
2039  //snap x coordinate
2040  int xRatio = static_cast< int >( ( scenePoint.x() - mSnapGridOffsetX ) / mSnapGridResolution + 0.5 ); //NOLINT
2041  int yRatio = static_cast< int >( ( yPage - mSnapGridOffsetY ) / mSnapGridResolution + 0.5 ); //NOLINT
2042 
2043  double xSnapped = xRatio * mSnapGridResolution + mSnapGridOffsetX;
2044  double ySnapped = yRatio * mSnapGridResolution + mSnapGridOffsetY + yOffset;
2045 
2046  //convert snap tolerance from pixels to mm
2047  double viewScaleFactor = graphicsView()->transform().m11();
2048  double alignThreshold = mSnapTolerance / viewScaleFactor;
2049 
2050  if ( fabs( xSnapped - scenePoint.x() ) > alignThreshold )
2051  {
2052  //snap distance is outside of tolerance
2053  xSnapped = scenePoint.x();
2054  }
2055  if ( fabs( ySnapped - scenePoint.y() ) > alignThreshold )
2056  {
2057  //snap distance is outside of tolerance
2058  ySnapped = scenePoint.y();
2059  }
2060 
2061  return QPointF( xSnapped, ySnapped );
2062 }
2063 
2064 QGraphicsLineItem *QgsComposition::addSnapLine()
2065 {
2066  QGraphicsLineItem *item = new QGraphicsLineItem();
2067  QPen linePen( Qt::SolidLine );
2068  linePen.setColor( Qt::red );
2069  // use a pen width of 0, since this activates a cosmetic pen
2070  // which doesn't scale with the composer and keeps a constant size
2071  linePen.setWidthF( 0 );
2072  item->setPen( linePen );
2073  item->setZValue( 100 );
2074  item->setVisible( mGuidesVisible );
2075  addItem( item );
2076  mSnapLines.push_back( item );
2077  return item;
2078 }
2079 
2080 void QgsComposition::removeSnapLine( QGraphicsLineItem *line )
2081 {
2082  removeItem( line );
2083  mSnapLines.removeAll( line );
2084  delete line;
2085 }
2086 
2088 {
2089  Q_FOREACH ( QGraphicsLineItem *line, mSnapLines )
2090  {
2091  removeItem( line );
2092  delete ( line );
2093  }
2094  mSnapLines.clear();
2095 }
2096 
2097 void QgsComposition::setSnapLinesVisible( const bool visible )
2098 {
2099  mGuidesVisible = visible;
2100  Q_FOREACH ( QGraphicsLineItem *line, mSnapLines )
2101  {
2102  line->setVisible( visible );
2103  }
2104 }
2105 
2107 {
2108  mPagesVisible = visible;
2109  update();
2110 }
2111 
2112 QGraphicsLineItem *QgsComposition::nearestSnapLine( const bool horizontal, const double x, const double y, const double tolerance,
2113  QList< QPair< QgsComposerItem *, QgsComposerItem::ItemPositionMode> > &snappedItems ) const
2114 {
2115  double minSqrDist = DBL_MAX;
2116  QGraphicsLineItem *item = nullptr;
2117  double currentXCoord = 0;
2118  double currentYCoord = 0;
2119  double currentSqrDist = 0;
2120  double sqrTolerance = tolerance * tolerance;
2121 
2122  snappedItems.clear();
2123 
2124  QList< QGraphicsLineItem * >::const_iterator it = mSnapLines.constBegin();
2125  for ( ; it != mSnapLines.constEnd(); ++it )
2126  {
2127  bool itemHorizontal = qgsDoubleNear( ( *it )->line().y2() - ( *it )->line().y1(), 0 );
2128  if ( horizontal && itemHorizontal )
2129  {
2130  currentYCoord = ( *it )->line().y1();
2131  currentSqrDist = ( y - currentYCoord ) * ( y - currentYCoord );
2132  }
2133  else if ( !horizontal && !itemHorizontal )
2134  {
2135  currentXCoord = ( *it )->line().x1();
2136  currentSqrDist = ( x - currentXCoord ) * ( x - currentXCoord );
2137  }
2138  else
2139  {
2140  continue;
2141  }
2142 
2143  if ( currentSqrDist < minSqrDist && currentSqrDist < sqrTolerance )
2144  {
2145  item = *it;
2146  minSqrDist = currentSqrDist;
2147  }
2148  }
2149 
2150  double itemTolerance = 0.0000001;
2151  if ( item )
2152  {
2153  //go through all the items to find items snapped to this snap line
2154  QList<QGraphicsItem *> itemList = items();
2155  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
2156  for ( ; itemIt != itemList.end(); ++itemIt )
2157  {
2158  QgsComposerItem *currentItem = dynamic_cast<QgsComposerItem *>( *itemIt );
2159  if ( !currentItem || currentItem->type() == QgsComposerItem::ComposerPaper )
2160  {
2161  continue;
2162  }
2163 
2164  if ( horizontal )
2165  {
2166  if ( qgsDoubleNear( currentYCoord, currentItem->pos().y() + currentItem->rect().top(), itemTolerance ) )
2167  {
2168  snappedItems.append( qMakePair( currentItem, QgsComposerItem::UpperMiddle ) );
2169  }
2170  else if ( qgsDoubleNear( currentYCoord, currentItem->pos().y() + currentItem->rect().center().y(), itemTolerance ) )
2171  {
2172  snappedItems.append( qMakePair( currentItem, QgsComposerItem::Middle ) );
2173  }
2174  else if ( qgsDoubleNear( currentYCoord, currentItem->pos().y() + currentItem->rect().bottom(), itemTolerance ) )
2175  {
2176  snappedItems.append( qMakePair( currentItem, QgsComposerItem::LowerMiddle ) );
2177  }
2178  }
2179  else
2180  {
2181  if ( qgsDoubleNear( currentXCoord, currentItem->pos().x(), itemTolerance ) )
2182  {
2183  snappedItems.append( qMakePair( currentItem, QgsComposerItem::MiddleLeft ) );
2184  }
2185  else if ( qgsDoubleNear( currentXCoord, currentItem->pos().x() + currentItem->rect().center().x(), itemTolerance ) )
2186  {
2187  snappedItems.append( qMakePair( currentItem, QgsComposerItem::Middle ) );
2188  }
2189  else if ( qgsDoubleNear( currentXCoord, currentItem->pos().x() + currentItem->rect().width(), itemTolerance ) )
2190  {
2191  snappedItems.append( qMakePair( currentItem, QgsComposerItem::MiddleRight ) );
2192  }
2193  }
2194  }
2195  }
2196 
2197  return item;
2198 }
2199 
2200 int QgsComposition::boundingRectOfSelectedItems( QRectF &bRect )
2201 {
2202  QList<QgsComposerItem *> selectedItems = selectedComposerItems();
2203  if ( selectedItems.size() < 1 )
2204  {
2205  return 1;
2206  }
2207 
2208  //set the box to the first item
2209  QgsComposerItem *currentItem = selectedItems.at( 0 );
2210  double minX = currentItem->pos().x();
2211  double minY = currentItem->pos().y();
2212  double maxX = minX + currentItem->rect().width();
2213  double maxY = minY + currentItem->rect().height();
2214 
2215  double currentMinX, currentMinY, currentMaxX, currentMaxY;
2216 
2217  for ( int i = 1; i < selectedItems.size(); ++i )
2218  {
2219  currentItem = selectedItems.at( i );
2220  currentMinX = currentItem->pos().x();
2221  currentMinY = currentItem->pos().y();
2222  currentMaxX = currentMinX + currentItem->rect().width();
2223  currentMaxY = currentMinY + currentItem->rect().height();
2224 
2225  if ( currentMinX < minX )
2226  minX = currentMinX;
2227  if ( currentMaxX > maxX )
2228  maxX = currentMaxX;
2229  if ( currentMinY < minY )
2230  minY = currentMinY;
2231  if ( currentMaxY > maxY )
2232  maxY = currentMaxY;
2233  }
2234 
2235  bRect.setTopLeft( QPointF( minX, minY ) );
2236  bRect.setBottomRight( QPointF( maxX, maxY ) );
2237  return 0;
2238 }
2239 
2241 {
2242  mSnapToGrid = b;
2243  updatePaperItems();
2244 }
2245 
2247 {
2248  mGridVisible = b;
2249  updatePaperItems();
2250 }
2251 
2253 {
2254  mSnapGridResolution = r;
2255  updatePaperItems();
2256 }
2257 
2258 void QgsComposition::setSnapGridOffsetX( const double offset )
2259 {
2260  mSnapGridOffsetX = offset;
2261  updatePaperItems();
2262 }
2263 
2264 void QgsComposition::setSnapGridOffsetY( const double offset )
2265 {
2266  mSnapGridOffsetY = offset;
2267  updatePaperItems();
2268 }
2269 
2270 void QgsComposition::setGridPen( const QPen &p )
2271 {
2272  mGridPen = p;
2273  //make sure grid is drawn using a zero-width cosmetic pen
2274  mGridPen.setWidthF( 0 );
2275  updatePaperItems();
2276 }
2277 
2279 {
2280  mGridStyle = s;
2281  updatePaperItems();
2282 }
2283 
2284 void QgsComposition::setBoundingBoxesVisible( const bool boundsVisible )
2285 {
2286  mBoundingBoxesVisible = boundsVisible;
2287 
2288  if ( mSelectionHandles )
2289  {
2290  mSelectionHandles->update();
2291  }
2292 }
2293 
2295 {
2296  //load new composer setting values
2297  loadSettings();
2298  //update any paper items to reflect new settings
2299  updatePaperItems();
2300 }
2301 
2302 void QgsComposition::loadSettings()
2303 {
2304  //read grid style, grid color and pen width from settings
2305  QgsSettings s;
2306 
2307  QString gridStyleString;
2308  gridStyleString = s.value( QStringLiteral( "/Composer/gridStyle" ), "Dots" ).toString();
2309 
2310  int gridRed, gridGreen, gridBlue, gridAlpha;
2311  gridRed = s.value( QStringLiteral( "/Composer/gridRed" ), 190 ).toInt();
2312  gridGreen = s.value( QStringLiteral( "/Composer/gridGreen" ), 190 ).toInt();
2313  gridBlue = s.value( QStringLiteral( "/Composer/gridBlue" ), 190 ).toInt();
2314  gridAlpha = s.value( QStringLiteral( "/Composer/gridAlpha" ), 100 ).toInt();
2315  QColor gridColor = QColor( gridRed, gridGreen, gridBlue, gridAlpha );
2316 
2317  mGridPen.setColor( gridColor );
2318  mGridPen.setWidthF( 0 );
2319 
2320  if ( gridStyleString == QLatin1String( "Dots" ) )
2321  {
2322  mGridStyle = Dots;
2323  }
2324  else if ( gridStyleString == QLatin1String( "Crosses" ) )
2325  {
2326  mGridStyle = Crosses;
2327  }
2328  else
2329  {
2330  mGridStyle = Solid;
2331  }
2332 }
2333 
2334 void QgsComposition::beginCommand( QgsComposerItem *item, const QString &commandText, const QgsComposerMergeCommand::Context c )
2335 {
2336  delete mActiveItemCommand;
2337  if ( !item )
2338  {
2339  mActiveItemCommand = nullptr;
2340  return;
2341  }
2342 
2344  {
2345  mActiveItemCommand = new QgsComposerItemCommand( item, commandText );
2346  }
2347  else
2348  {
2349  mActiveItemCommand = new QgsComposerMergeCommand( c, item, commandText );
2350  }
2351  mActiveItemCommand->savePreviousState();
2352 }
2353 
2355 {
2356  if ( mActiveItemCommand )
2357  {
2358  mActiveItemCommand->saveAfterState();
2359  if ( mActiveItemCommand->containsChange() ) //protect against empty commands
2360  {
2361  mUndoStack->push( mActiveItemCommand );
2362  mProject->setDirty( true );
2363  }
2364  else
2365  {
2366  delete mActiveItemCommand;
2367  }
2368  mActiveItemCommand = nullptr;
2369  }
2370 }
2371 
2373 {
2374  delete mActiveItemCommand;
2375  mActiveItemCommand = nullptr;
2376 }
2377 
2379 {
2380  delete mActiveMultiFrameCommand;
2381 
2382  if ( !multiFrame )
2383  {
2384  mActiveMultiFrameCommand = nullptr;
2385  return;
2386  }
2387 
2389  {
2390  mActiveMultiFrameCommand = new QgsComposerMultiFrameCommand( multiFrame, text );
2391  }
2392  else
2393  {
2394  mActiveMultiFrameCommand = new QgsComposerMultiFrameMergeCommand( c, multiFrame, text );
2395  }
2396  mActiveMultiFrameCommand->savePreviousState();
2397 }
2398 
2400 {
2401  if ( mActiveMultiFrameCommand )
2402  {
2403  mActiveMultiFrameCommand->saveAfterState();
2404  if ( mActiveMultiFrameCommand->containsChange() )
2405  {
2406  mUndoStack->push( mActiveMultiFrameCommand );
2407  mProject->setDirty( true );
2408  }
2409  else
2410  {
2411  delete mActiveMultiFrameCommand;
2412  }
2413  mActiveMultiFrameCommand = nullptr;
2414  }
2415 }
2416 
2418 {
2419  delete mActiveMultiFrameCommand;
2420  mActiveMultiFrameCommand = nullptr;
2421 }
2422 
2424 {
2425  mMultiFrames.insert( multiFrame );
2426 
2427  updateBounds();
2428 }
2429 
2431 {
2432  mMultiFrames.remove( multiFrame );
2433 
2434  updateBounds();
2435 }
2436 
2438 {
2439  addItem( arrow );
2440 
2441  updateBounds();
2443 
2444  emit itemAdded( arrow );
2445 }
2446 
2448 {
2449  addItem( polygon );
2450 
2451  updateBounds();
2452  connect( polygon, &QgsComposerItem::sizeChanged, this, &QgsComposition::updateBounds );
2453 
2454  emit itemAdded( polygon );
2455 }
2456 
2458 {
2459  addItem( polyline );
2460 
2461  updateBounds();
2462  connect( polyline, &QgsComposerItem::sizeChanged, this, &QgsComposition::updateBounds );
2463 
2464  emit itemAdded( polyline );
2465 }
2466 
2468 {
2469  addItem( label );
2470 
2471  updateBounds();
2473 
2474  emit itemAdded( label );
2475 }
2476 
2478 {
2479  addItem( map );
2480  updateBounds();
2482 
2483  emit itemAdded( map );
2484 }
2485 
2487 {
2488  addItem( scaleBar );
2489 
2490  updateBounds();
2491  connect( scaleBar, &QgsComposerItem::sizeChanged, this, &QgsComposition::updateBounds );
2492 
2493  emit itemAdded( scaleBar );
2494 }
2495 
2497 {
2498  addItem( legend );
2499 
2500  updateBounds();
2501  connect( legend, &QgsComposerItem::sizeChanged, this, &QgsComposition::updateBounds );
2502 
2503  emit itemAdded( legend );
2504 }
2505 
2507 {
2508  addItem( picture );
2509 
2510  updateBounds();
2511  connect( picture, &QgsComposerItem::sizeChanged, this, &QgsComposition::updateBounds );
2512 
2513  emit itemAdded( picture );
2514 }
2515 
2517 {
2518  addItem( shape );
2519 
2520  updateBounds();
2522 
2523  emit itemAdded( shape );
2524 }
2525 
2527 {
2528  addItem( frame );
2529 
2530  updateBounds();
2532 
2533  emit itemAdded( frame );
2534 }
2535 
2537 {
2538  addItem( frame );
2539 
2540  updateBounds();
2542 
2543  emit itemAdded( frame );
2544 }
2545 
2546 /* public */
2547 void QgsComposition::removeComposerItem( QgsComposerItem *item, const bool createCommand, const bool removeGroupItems )
2548 {
2549  QgsComposerMap *map = dynamic_cast<QgsComposerMap *>( item );
2550 
2551  if ( !map || !map->isDrawing() ) //don't delete a composer map while it draws
2552  {
2553  mItemsModel->setItemRemoved( item );
2554  removeItem( item );
2555  emit itemRemoved( item );
2556 
2557  QgsDebugMsg( QString( "removeComposerItem called, createCommand:%1 removeGroupItems:%2" )
2558  .arg( createCommand ).arg( removeGroupItems ) );
2559 
2560  QgsComposerItemGroup *itemGroup = dynamic_cast<QgsComposerItemGroup *>( item );
2561  if ( itemGroup && removeGroupItems )
2562  {
2563  QgsDebugMsg( QString( "itemGroup && removeGroupItems" ) );
2564 
2565  // Takes ownership of itemGroup
2566  QgsAddRemoveItemCommand *parentCommand = new QgsAddRemoveItemCommand(
2567  QgsAddRemoveItemCommand::Removed, itemGroup, this,
2568  tr( "Remove item group" ) );
2569  connectAddRemoveCommandSignals( parentCommand );
2570 
2571  //add add/remove item command for every item in the group
2572  QSet<QgsComposerItem *> groupedItems = itemGroup->items();
2573  QgsDebugMsg( QString( "itemGroup contains %1 items" ) .arg( groupedItems.size() ) );
2574  QSet<QgsComposerItem *>::iterator it = groupedItems.begin();
2575  for ( ; it != groupedItems.end(); ++it )
2576  {
2577  mItemsModel->setItemRemoved( *it );
2578  removeItem( *it );
2579  QgsAddRemoveItemCommand *subcommand = new QgsAddRemoveItemCommand( QgsAddRemoveItemCommand::Removed, *it, this, QLatin1String( "" ), parentCommand );
2580  connectAddRemoveCommandSignals( subcommand );
2581  emit itemRemoved( *it );
2582  }
2583 
2584  undoStack()->push( parentCommand );
2585  }
2586  else
2587  {
2588  bool frameItem = ( item->type() == QgsComposerItem::ComposerFrame );
2589  QgsComposerMultiFrame *multiFrame = nullptr;
2590  if ( createCommand )
2591  {
2592  if ( frameItem ) //multiframe tracks item changes
2593  {
2594  multiFrame = static_cast<QgsComposerFrame *>( item )->multiFrame();
2595  item->beginItemCommand( tr( "Frame deleted" ) );
2596  item->endItemCommand();
2597  }
2598  else
2599  {
2600  pushAddRemoveCommand( item, tr( "Item deleted" ), QgsAddRemoveItemCommand::Removed );
2601  }
2602  }
2603 
2604  //check if there are frames left. If not, remove the multi frame
2605  if ( frameItem && multiFrame )
2606  {
2607  if ( multiFrame->frameCount() < 1 )
2608  {
2609  removeMultiFrame( multiFrame );
2610  if ( createCommand )
2611  {
2613  multiFrame, this, tr( "Multiframe removed" ) );
2614  undoStack()->push( command );
2615  }
2616  else
2617  {
2618  delete multiFrame;
2619  }
2620  }
2621  }
2622  }
2623  }
2624 
2625  updateBounds();
2626 }
2627 
2629 {
2630  QgsAddRemoveItemCommand *c = new QgsAddRemoveItemCommand( state, item, this, text );
2631  connectAddRemoveCommandSignals( c );
2632  undoStack()->push( c );
2633  mProject->setDirty( true );
2634 }
2635 
2636 void QgsComposition::connectAddRemoveCommandSignals( QgsAddRemoveItemCommand *c )
2637 {
2638  if ( !c )
2639  {
2640  return;
2641  }
2642 
2645 }
2646 
2648 {
2649  //cast and send proper signal
2650  item->setSelected( true );
2651  switch ( item->type() )
2652  {
2663 
2664  emit itemAdded( item );
2665  emit selectedItemChanged( item );
2666  return;
2667 
2668  }
2669 
2670  QgsComposerItemGroup *group = dynamic_cast<QgsComposerItemGroup *>( item );
2671  if ( group )
2672  {
2673  emit composerItemGroupAdded( group );
2674  }
2675 }
2676 
2677 void QgsComposition::updatePaperItems()
2678 {
2679  Q_FOREACH ( QgsPaperItem *page, mPages )
2680  {
2681  page->update();
2682  }
2683 }
2684 
2685 void QgsComposition::addPaperItem()
2686 {
2687  double paperHeight = this->paperHeight();
2688  double paperWidth = this->paperWidth();
2689  double currentY = paperHeight * mPages.size() + mPages.size() * mSpaceBetweenPages; //add 10mm visible space between pages
2690  QgsPaperItem *paperItem = new QgsPaperItem( 0, currentY, paperWidth, paperHeight, this ); //default size A4
2691  paperItem->setBrush( Qt::white );
2692  addItem( paperItem );
2693  paperItem->setZValue( 0 );
2694  mPages.push_back( paperItem );
2695 }
2696 
2697 void QgsComposition::removePaperItems()
2698 {
2699  qDeleteAll( mPages );
2700  mPages.clear();
2701 }
2702 
2703 void QgsComposition::deleteAndRemoveMultiFrames()
2704 {
2705  qDeleteAll( mMultiFrames );
2706  mMultiFrames.clear();
2707 }
2708 
2709 #ifndef QT_NO_PRINTER
2710 
2711 void QgsComposition::beginPrintAsPDF( QPrinter &printer, const QString &file )
2712 {
2713  printer.setOutputFileName( file );
2714  // setOutputFormat should come after setOutputFileName, which auto-sets format to QPrinter::PdfFormat.
2715  // [LS] This should be QPrinter::NativeFormat for Mac, otherwise fonts are not embed-able
2716  // and text is not searchable; however, there are several bugs with <= Qt 4.8.5, 5.1.1, 5.2.0:
2717  // https://bugreports.qt-project.org/browse/QTBUG-10094 - PDF font embedding fails
2718  // https://bugreports.qt-project.org/browse/QTBUG-33583 - PDF output converts text to outline
2719  // Also an issue with PDF paper size using QPrinter::NativeFormat on Mac (always outputs portrait letter-size)
2720  printer.setOutputFormat( QPrinter::PdfFormat );
2721 
2722  refreshPageSize();
2723  //must set orientation to portrait before setting paper size, otherwise size will be flipped
2724  //for landscape sized outputs (#11352)
2725  printer.setOrientation( QPrinter::Portrait );
2726  printer.setPaperSize( QSizeF( paperWidth(), paperHeight() ), QPrinter::Millimeter );
2727 
2728  // TODO: add option for this in Composer
2729  // May not work on Windows or non-X11 Linux. Works fine on Mac using QPrinter::NativeFormat
2730  //printer.setFontEmbeddingEnabled( true );
2731 
2732  QgsPaintEngineHack::fixEngineFlags( printer.paintEngine() );
2733 }
2734 
2735 bool QgsComposition::exportAsPDF( const QString &file )
2736 {
2737  QPrinter printer;
2738  beginPrintAsPDF( printer, file );
2739  return print( printer );
2740 }
2741 
2742 #endif
2743 
2744 void QgsComposition::georeferenceOutput( const QString &file, QgsComposerMap *map,
2745  const QRectF &exportRegion, double dpi ) const
2746 {
2747  if ( !map )
2748  map = referenceMap();
2749 
2750  if ( !map )
2751  return; // no reference map
2752 
2753  if ( dpi < 0 )
2754  dpi = printResolution();
2755 
2756  double *t = computeGeoTransform( map, exportRegion, dpi );
2757  if ( !t )
2758  return;
2759 
2760  // important - we need to manually specify the DPI in advance, as GDAL will otherwise
2761  // assume a DPI of 150
2762  CPLSetConfigOption( "GDAL_PDF_DPI", QString::number( dpi ).toLocal8Bit().constData() );
2763  GDALDatasetH outputDS = GDALOpen( file.toLocal8Bit().constData(), GA_Update );
2764  if ( outputDS )
2765  {
2766  GDALSetGeoTransform( outputDS, t );
2767 #if 0
2768  //TODO - metadata can be set here, e.g.:
2769  GDALSetMetadataItem( outputDS, "AUTHOR", "me", nullptr );
2770 #endif
2771  GDALSetProjection( outputDS, map->crs().toWkt().toLocal8Bit().constData() );
2772  GDALClose( outputDS );
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 = sin( alpha );
3015  double cosAlpha = 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] = cos( alpha );
3155  r[1] = -sin( alpha );
3156  r[2] = xCenter * ( 1 - cos( alpha ) ) + yCenter * sin( alpha );
3157  r[3] = sin( alpha );
3158  r[4] = cos( alpha );
3159  r[5] = - xCenter * sin( alpha ) + yCenter * ( 1 - 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  QVariant exprVal;
3211  //in order of precedence - first consider predefined page size
3212  bool ok = false;
3213  QString presetString = mDataDefinedProperties.valueAsString( QgsComposerObject::PresetPaperSize, *evalContext, QString(), &ok );
3214  if ( ok && !presetString.isEmpty() )
3215  {
3216  double widthD = 0;
3217  double heightD = 0;
3218  if ( QgsComposerUtils::decodePresetPaperSize( presetString, widthD, heightD ) )
3219  {
3220  pageWidth = widthD;
3221  pageHeight = heightD;
3222  }
3223  }
3224 
3225  //which is overwritten by data defined width/height
3226  pageWidth = mDataDefinedProperties.valueAsDouble( QgsComposerObject::PaperWidth, *evalContext, pageWidth );
3227  pageHeight = mDataDefinedProperties.valueAsDouble( QgsComposerObject::PaperHeight, *evalContext, pageHeight );
3228 
3229  //which is finally overwritten by data defined orientation
3230  QString orientationString = mDataDefinedProperties.valueAsString( QgsComposerObject::PaperOrientation, *evalContext, QString(), &ok );
3231  if ( ok && !orientationString.isEmpty() )
3232  {
3233  orientationString = orientationString.trimmed();
3234  QgsComposition::PaperOrientation orientation = QgsComposerUtils::decodePaperOrientation( orientationString, ok );
3235  QgsDebugMsg( QString( "exprVal Paper Orientation:%1" ).arg( orientationString ) );
3236  if ( ok )
3237  {
3238  double heightD, widthD;
3239  if ( orientation == QgsComposition::Portrait )
3240  {
3241  heightD = qMax( pageHeight, pageWidth );
3242  widthD = qMin( pageHeight, pageWidth );
3243  }
3244  else
3245  {
3246  heightD = qMin( pageHeight, pageWidth );
3247  widthD = qMax( pageHeight, pageWidth );
3248  }
3249  pageWidth = widthD;
3250  pageHeight = heightD;
3251  }
3252  }
3253 
3254  setPaperSize( pageWidth, pageHeight );
3255 }
3256 
3257 void QgsComposition::setCustomProperty( const QString &key, const QVariant &value )
3258 {
3259  mCustomProperties.setValue( key, value );
3260 
3261  if ( key.startsWith( QLatin1String( "variable" ) ) )
3262  emit variablesChanged();
3263 }
3264 
3265 QVariant QgsComposition::customProperty( const QString &key, const QVariant &defaultValue ) const
3266 {
3267  return mCustomProperties.value( key, defaultValue );
3268 }
3269 
3270 void QgsComposition::removeCustomProperty( const QString &key )
3271 {
3272  mCustomProperties.remove( key );
3273 }
3274 
3276 {
3277  return mCustomProperties.keys();
3278 }
3279 
3281 {
3286  if ( mAtlasComposition.enabled() )
3287  {
3288  context.appendScope( QgsExpressionContextUtils::atlasScope( &mAtlasComposition ) );
3289  }
3290  return context;
3291 }
3292 
3293 void QgsComposition::prepareAllDataDefinedExpressions()
3294 {
3296  mDataDefinedProperties.prepare( context );
3297 }
3298 
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:408
Item representing the paper.
Definition: qgspaperitem.h:43
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:38
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:54
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:47
static QgsFillSymbol * createSimple(const QgsStringMap &properties)
Create a fill symbol with one symbol layer: SimpleFill with specified properties. ...
Definition: qgssymbol.cpp:1075
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:340
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:203
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:1832
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:118
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) ...
#define M_PI
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:78
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)
void * GDALDatasetH
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.
double ANALYSIS_EXPORT max(double x, double y)
Returns the maximum of two doubles or the first argument if both are equal.
Definition: MathUtils.cc:437
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:46
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:106
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:96
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:101
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:146
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:125
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)