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