QGIS API Documentation  2.2.0-Valmiera
 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 "qgscomposerarrow.h"
19 #include "qgscomposerframe.h"
20 #include "qgscomposerhtml.h"
21 #include "qgscomposerlabel.h"
22 #include "qgscomposerlegend.h"
23 #include "qgscomposermap.h"
25 #include "qgscomposeritemgroup.h"
26 #include "qgscomposerpicture.h"
27 #include "qgscomposerscalebar.h"
28 #include "qgscomposershape.h"
29 #include "qgscomposerlabel.h"
33 #include "qgspaintenginehack.h"
34 #include "qgspaperitem.h"
35 #include "qgsproject.h"
36 #include "qgsgeometry.h"
37 #include "qgsvectorlayer.h"
38 #include "qgsvectordataprovider.h"
39 #include "qgsexpression.h"
40 #include "qgssymbolv2.h"
41 #include "qgssymbollayerv2utils.h"
42 
43 #include <QDomDocument>
44 #include <QDomElement>
45 #include <QGraphicsRectItem>
46 #include <QPainter>
47 #include <QPrinter>
48 #include <QSettings>
49 #include <QDir>
50 
51 #include <limits>
52 
54  : QGraphicsScene( 0 )
55  , mMapRenderer( mapRenderer )
56  , mPlotStyle( QgsComposition::Preview )
57  , mPageWidth( 297 )
58  , mPageHeight( 210 )
59  , mSpaceBetweenPages( 10 )
60  , mPageStyleSymbol( 0 )
61  , mPrintAsRaster( false )
62  , mGenerateWorldFile( false )
63  , mWorldFileMap( 0 )
64  , mUseAdvancedEffects( true )
65  , mSnapToGrid( false )
66  , mGridVisible( false )
67  , mSnapGridResolution( 0 )
68  , mSnapGridTolerance( 0 )
69  , mSnapGridOffsetX( 0 )
70  , mSnapGridOffsetY( 0 )
71  , mAlignmentSnap( true )
72  , mGuidesVisible( true )
73  , mSmartGuides( true )
74  , mAlignmentSnapTolerance( 0 )
75  , mSelectionHandles( 0 )
76  , mActiveItemCommand( 0 )
77  , mActiveMultiFrameCommand( 0 )
78  , mAtlasComposition( this )
79  , mAtlasMode( QgsComposition::AtlasOff )
80  , mPreventCursorChange( false )
81 {
82  setBackgroundBrush( QColor( 215, 215, 215 ) );
84  addPaperItem();
85 
86  updateBounds();
87 
88  //add mouse selection handles to composition, and initially hide
90  addItem( mSelectionHandles );
91  mSelectionHandles->hide();
92  mSelectionHandles->setZValue( 500 );
93 
94  mPrintResolution = 300; //hardcoded default
95 
96  //load default composition settings
97  loadDefaults();
98  loadSettings();
99 }
100 
102  : QGraphicsScene( 0 ),
103  mMapRenderer( 0 ),
104  mPlotStyle( QgsComposition::Preview ),
105  mPageWidth( 297 ),
106  mPageHeight( 210 ),
107  mSpaceBetweenPages( 10 ),
108  mPageStyleSymbol( 0 ),
109  mPrintAsRaster( false ),
110  mGenerateWorldFile( false ),
111  mWorldFileMap( 0 ),
112  mUseAdvancedEffects( true ),
113  mSnapToGrid( false ),
114  mGridVisible( false ),
115  mSnapGridResolution( 0 ),
116  mSnapGridTolerance( 0 ),
117  mSnapGridOffsetX( 0 ),
118  mSnapGridOffsetY( 0 ),
119  mAlignmentSnap( true ),
120  mGuidesVisible( true ),
121  mSmartGuides( true ),
122  mAlignmentSnapTolerance( 0 ),
123  mSelectionHandles( 0 ),
124  mActiveItemCommand( 0 ),
125  mActiveMultiFrameCommand( 0 ),
126  mAtlasComposition( this ),
127  mAtlasMode( QgsComposition::AtlasOff ),
128  mPreventCursorChange( false )
129 {
130  //load default composition settings
131  loadDefaults();
132  loadSettings();
133 }
134 
136 {
139 
140  // make sure that all composer items are removed before
141  // this class is deconstructed - to avoid segfaults
142  // when composer items access in destructor composition that isn't valid anymore
143  clear();
144  delete mActiveItemCommand;
146  delete mPageStyleSymbol;
147 }
148 
150 {
151  QSettings settings;
152  mSnapGridResolution = settings.value( "/Composer/defaultSnapGridResolution", 10.0 ).toDouble();
153  mSnapGridTolerance = settings.value( "/Composer/defaultSnapGridTolerance", 2 ).toDouble();
154  mSnapGridOffsetX = settings.value( "/Composer/defaultSnapGridOffsetX", 0 ).toDouble();
155  mSnapGridOffsetY = settings.value( "/Composer/defaultSnapGridOffsetY", 0 ).toDouble();
156  mAlignmentSnapTolerance = settings.value( "/Composer/defaultSnapGuideTolerance", 2 ).toDouble();
157 }
158 
160 {
161  setSceneRect( compositionBounds() );
162 }
163 
165 {
166  //start with an empty rectangle
167  QRectF bounds = QRectF( 0, 0, 0, 0 );
168 
169  //add all QgsComposerItems and QgsPaperItems which are in the composition
170  QList<QGraphicsItem *> itemList = items();
171  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
172  for ( ; itemIt != itemList.end(); ++itemIt )
173  {
174  const QgsComposerItem* composerItem = dynamic_cast<const QgsComposerItem *>( *itemIt );
175  const QgsPaperItem* paperItem = dynamic_cast<const QgsPaperItem*>( *itemIt );
176  if (( composerItem || paperItem ) )
177  {
178  //expand bounds with current item's bounds
179  bounds = bounds.united(( *itemIt )->sceneBoundingRect() );
180  }
181  }
182 
183  //finally, expand bounds out by 5% page size to give a bit of a margin
184  bounds.adjust( -mPageWidth * 0.05, -mPageWidth * 0.05, mPageWidth * 0.05, mPageWidth * 0.05 );
185 
186  return bounds;
187 }
188 
189 void QgsComposition::setPaperSize( double width, double height )
190 {
191  mPageWidth = width;
192  mPageHeight = height;
193  double currentY = 0;
194  for ( int i = 0; i < mPages.size(); ++i )
195  {
196  mPages.at( i )->setSceneRect( QRectF( 0, currentY, width, height ) );
197  currentY += ( height + mSpaceBetweenPages );
198  }
199  updateBounds();
200  emit paperSizeChanged();
201 }
202 
204 {
205  return mPageHeight;
206 }
207 
209 {
210  return mPageWidth;
211 }
212 
214 {
215  int currentPages = numPages();
216  int diff = pages - currentPages;
217  if ( diff >= 0 )
218  {
219  for ( int i = 0; i < diff; ++i )
220  {
221  addPaperItem();
222  }
223  }
224  else
225  {
226  diff = -diff;
227  for ( int i = 0; i < diff; ++i )
228  {
229  delete mPages.last();
230  mPages.removeLast();
231  }
232  }
233 
234  //update the corresponding variable
235  QgsExpression::setSpecialColumn( "$numpages", QVariant(( int )numPages() ) );
236 
237  updateBounds();
238 
239  emit nPagesChanged();
240 }
241 
243 {
244  return mPages.size();
245 }
246 
248 {
249  delete mPageStyleSymbol;
250  mPageStyleSymbol = symbol;
251 }
252 
254 {
255  delete mPageStyleSymbol;
256  QgsStringMap properties;
257  properties.insert( "color", "white" );
258  properties.insert( "style", "solid" );
259  properties.insert( "style_border", "no" );
261 }
262 
263 QPointF QgsComposition::positionOnPage( const QPointF & position ) const
264 {
265  double y;
266  if ( position.y() > ( mPages.size() - 1 ) * ( paperHeight() + spaceBetweenPages() ) )
267  {
268  //y coordinate is greater then the end of the last page, so return distance between
269  //top of last page and y coordinate
270  y = position.y() - ( mPages.size() - 1 ) * ( paperHeight() + spaceBetweenPages() );
271  }
272  else
273  {
274  //y coordinate is less then the end of the last page
275  y = fmod( position.y(), ( paperHeight() + spaceBetweenPages() ) );
276  }
277  return QPointF( position.x(), y );
278 }
279 
280 int QgsComposition::pageNumberForPoint( const QPointF & position ) const
281 {
282  int pageNumber = qFloor( position.y() / ( paperHeight() + spaceBetweenPages() ) ) + 1;
283  pageNumber = pageNumber < 1 ? 1 : pageNumber;
284  pageNumber = pageNumber > mPages.size() ? mPages.size() : pageNumber;
285  return pageNumber;
286 }
287 
288 void QgsComposition::setStatusMessage( const QString & message )
289 {
290  emit statusMsgChanged( message );
291 }
292 
294 {
295  return composerItemAt( position, 0 );
296 }
297 
298 QgsComposerItem* QgsComposition::composerItemAt( const QPointF & position, const QgsComposerItem* belowItem )
299 {
300  //get a list of items which intersect the specified position, in descending z order
301  QList<QGraphicsItem*> itemList;
302  itemList = items( position, Qt::IntersectsItemShape, Qt::DescendingOrder );
303  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
304 
305  bool foundBelowItem = false;
306  for ( ; itemIt != itemList.end(); ++itemIt )
307  {
308  QgsComposerItem* composerItem = dynamic_cast<QgsComposerItem *>( *itemIt );
309  QgsPaperItem* paperItem = dynamic_cast<QgsPaperItem*>( *itemIt );
310  if ( composerItem && !paperItem )
311  {
312  // If we are not checking for a an item below a specified item, or if we've
313  // already found that item, then we've found our target
314  if ( ! belowItem || foundBelowItem )
315  {
316  return composerItem;
317  }
318  else
319  {
320  if ( composerItem == belowItem )
321  {
322  //Target item is next in list
323  foundBelowItem = true;
324  }
325  }
326  }
327  }
328  return 0;
329 }
330 
331 int QgsComposition::pageNumberAt( const QPointF& position ) const
332 {
333  return position.y() / ( paperHeight() + spaceBetweenPages() );
334 }
335 
337 {
338  return pageNumberAt( QPointF( item->pos().x(), item->pos().y() ) );
339 }
340 
341 QList<QgsComposerItem*> QgsComposition::selectedComposerItems()
342 {
343  QList<QgsComposerItem*> composerItemList;
344 
345  QList<QGraphicsItem *> graphicsItemList = selectedItems();
346  QList<QGraphicsItem *>::iterator itemIter = graphicsItemList.begin();
347 
348  for ( ; itemIter != graphicsItemList.end(); ++itemIter )
349  {
350  QgsComposerItem* composerItem = dynamic_cast<QgsComposerItem *>( *itemIter );
351  if ( composerItem )
352  {
353  composerItemList.push_back( composerItem );
354  }
355  }
356 
357  return composerItemList;
358 }
359 
360 QList<const QgsComposerMap*> QgsComposition::composerMapItems() const
361 {
362  QList<const QgsComposerMap*> resultList;
363 
364  QList<QGraphicsItem *> itemList = items();
365  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
366  for ( ; itemIt != itemList.end(); ++itemIt )
367  {
368  const QgsComposerMap* composerMap = dynamic_cast<const QgsComposerMap *>( *itemIt );
369  if ( composerMap )
370  {
371  resultList.push_back( composerMap );
372  }
373  }
374 
375  return resultList;
376 }
377 
379 {
380  QList<QGraphicsItem *> itemList = items();
381  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
382  for ( ; itemIt != itemList.end(); ++itemIt )
383  {
384  const QgsComposerMap* composerMap = dynamic_cast<const QgsComposerMap *>( *itemIt );
385  if ( composerMap )
386  {
387  if ( composerMap->id() == id )
388  {
389  return composerMap;
390  }
391  }
392  }
393  return 0;
394 }
395 
397 {
398  // an html item will be a composer frame and if it is we can try to get
399  // its multiframe parent and then try to cast that to a composer html
400  const QgsComposerFrame* composerFrame =
401  dynamic_cast<const QgsComposerFrame *>( item );
402  if ( composerFrame )
403  {
404  const QgsComposerMultiFrame * mypMultiFrame = composerFrame->multiFrame();
405  const QgsComposerHtml* composerHtml =
406  dynamic_cast<const QgsComposerHtml *>( mypMultiFrame );
407  if ( composerHtml )
408  {
409  return composerHtml;
410  }
411  }
412  return 0;
413 }
414 
416 {
417  QList<QGraphicsItem *> itemList = items();
418  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
419  for ( ; itemIt != itemList.end(); ++itemIt )
420  {
421  const QgsComposerItem* mypItem = dynamic_cast<const QgsComposerItem *>( *itemIt );
422  if ( mypItem )
423  {
424  if ( mypItem->id() == theId )
425  {
426  return mypItem;
427  }
428  }
429  }
430  return 0;
431 }
432 
433 #if 0
434 const QgsComposerItem* QgsComposition::getComposerItemByUuid( QString theUuid, bool inAllComposers ) const
435 {
436  //This does not work since it seems impossible to get the QgisApp::instance() from here... Is there a workaround ?
437  QSet<QgsComposer*> composers = QSet<QgsComposer*>();
438 
439  if ( inAllComposers )
440  {
441  composers = QgisApp::instance()->printComposers();
442  }
443  else
444  {
445  composers.insert( this )
446  }
447 
448  QSet<QgsComposer*>::const_iterator it = composers.constBegin();
449  for ( ; it != composers.constEnd(); ++it )
450  {
451  QList<QGraphicsItem *> itemList = ( *it )->items();
452  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
453  for ( ; itemIt != itemList.end(); ++itemIt )
454  {
455  const QgsComposerItem* mypItem = dynamic_cast<const QgsComposerItem *>( *itemIt );
456  if ( mypItem )
457  {
458  if ( mypItem->uuid() == theUuid )
459  {
460  return mypItem;
461  }
462  }
463  }
464  }
465 
466  return 0;
467 }
468 #endif
469 
471 {
472  QList<QGraphicsItem *> itemList = items();
473  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
474  for ( ; itemIt != itemList.end(); ++itemIt )
475  {
476  const QgsComposerItem* mypItem = dynamic_cast<const QgsComposerItem *>( *itemIt );
477  if ( mypItem )
478  {
479  if ( mypItem->uuid() == theUuid )
480  {
481  return mypItem;
482  }
483  }
484  }
485 
486  return 0;
487 }
488 
489 
490 void QgsComposition::setUseAdvancedEffects( bool effectsEnabled )
491 {
492  mUseAdvancedEffects = effectsEnabled;
493 
494  //toggle effects for all composer items
495  QList<QGraphicsItem*> itemList = items();
496  QList<QGraphicsItem*>::const_iterator itemIt = itemList.constBegin();
497  for ( ; itemIt != itemList.constEnd(); ++itemIt )
498  {
499  QgsComposerItem* composerItem = dynamic_cast<QgsComposerItem*>( *itemIt );
500  if ( composerItem )
501  {
502  composerItem->setEffectsEnabled( effectsEnabled );
503  }
504  }
505 }
506 
507 int QgsComposition::pixelFontSize( double pointSize ) const
508 {
509  //in QgsComposition, one unit = one mm
510  double sizeMillimeters = pointSize * 0.3527;
511  return qRound( sizeMillimeters ); //round to nearest mm
512 }
513 
514 double QgsComposition::pointFontSize( int pixelSize ) const
515 {
516  double sizePoint = pixelSize / 0.3527;
517  return sizePoint;
518 }
519 
520 bool QgsComposition::writeXML( QDomElement& composerElem, QDomDocument& doc )
521 {
522  if ( composerElem.isNull() )
523  {
524  return false;
525  }
526 
527  QDomElement compositionElem = doc.createElement( "Composition" );
528  compositionElem.setAttribute( "paperWidth", QString::number( mPageWidth ) );
529  compositionElem.setAttribute( "paperHeight", QString::number( mPageHeight ) );
530  compositionElem.setAttribute( "numPages", mPages.size() );
531 
532  QDomElement pageStyleElem = QgsSymbolLayerV2Utils::saveSymbol( QString(), mPageStyleSymbol, doc );
533  compositionElem.appendChild( pageStyleElem );
534 
535  //snapping
536  if ( mSnapToGrid )
537  {
538  compositionElem.setAttribute( "snapping", "1" );
539  }
540  else
541  {
542  compositionElem.setAttribute( "snapping", "0" );
543  }
544  if ( mGridVisible )
545  {
546  compositionElem.setAttribute( "gridVisible", "1" );
547  }
548  else
549  {
550  compositionElem.setAttribute( "gridVisible", "0" );
551  }
552  compositionElem.setAttribute( "snapGridResolution", QString::number( mSnapGridResolution ) );
553  compositionElem.setAttribute( "snapGridTolerance", QString::number( mSnapGridTolerance ) );
554  compositionElem.setAttribute( "snapGridOffsetX", QString::number( mSnapGridOffsetX ) );
555  compositionElem.setAttribute( "snapGridOffsetY", QString::number( mSnapGridOffsetY ) );
556 
557  //custom snap lines
558  QList< QGraphicsLineItem* >::const_iterator snapLineIt = mSnapLines.constBegin();
559  for ( ; snapLineIt != mSnapLines.constEnd(); ++snapLineIt )
560  {
561  QDomElement snapLineElem = doc.createElement( "SnapLine" );
562  QLineF line = ( *snapLineIt )->line();
563  snapLineElem.setAttribute( "x1", QString::number( line.x1() ) );
564  snapLineElem.setAttribute( "y1", QString::number( line.y1() ) );
565  snapLineElem.setAttribute( "x2", QString::number( line.x2() ) );
566  snapLineElem.setAttribute( "y2", QString::number( line.y2() ) );
567  compositionElem.appendChild( snapLineElem );
568  }
569 
570  compositionElem.setAttribute( "printResolution", mPrintResolution );
571  compositionElem.setAttribute( "printAsRaster", mPrintAsRaster );
572 
573  compositionElem.setAttribute( "generateWorldFile", mGenerateWorldFile ? 1 : 0 );
575  {
576  compositionElem.setAttribute( "worldFileMap", mWorldFileMap->id() );
577  }
578 
579  compositionElem.setAttribute( "alignmentSnap", mAlignmentSnap ? 1 : 0 );
580  compositionElem.setAttribute( "guidesVisible", mGuidesVisible ? 1 : 0 );
581  compositionElem.setAttribute( "smartGuides", mSmartGuides ? 1 : 0 );
582  compositionElem.setAttribute( "alignmentSnapTolerance", mAlignmentSnapTolerance );
583 
584  //save items except paper items and frame items (they are saved with the corresponding multiframe)
585  QList<QGraphicsItem*> itemList = items();
586  QList<QGraphicsItem*>::const_iterator itemIt = itemList.constBegin();
587  for ( ; itemIt != itemList.constEnd(); ++itemIt )
588  {
589  const QgsComposerItem* composerItem = dynamic_cast<const QgsComposerItem*>( *itemIt );
590  if ( composerItem )
591  {
592  if ( composerItem->type() != QgsComposerItem::ComposerPaper && composerItem->type() != QgsComposerItem::ComposerFrame )
593  {
594  composerItem->writeXML( compositionElem, doc );
595  }
596  }
597  }
598 
599  //save multiframes
600  QSet<QgsComposerMultiFrame*>::const_iterator multiFrameIt = mMultiFrames.constBegin();
601  for ( ; multiFrameIt != mMultiFrames.constEnd(); ++multiFrameIt )
602  {
603  ( *multiFrameIt )->writeXML( compositionElem, doc );
604  }
605  composerElem.appendChild( compositionElem );
606 
607  return true;
608 }
609 
610 bool QgsComposition::readXML( const QDomElement& compositionElem, const QDomDocument& doc )
611 {
612  Q_UNUSED( doc );
613  if ( compositionElem.isNull() )
614  {
615  return false;
616  }
617 
618  //create pages
619  bool widthConversionOk, heightConversionOk;
620  mPageWidth = compositionElem.attribute( "paperWidth" ).toDouble( &widthConversionOk );
621  mPageHeight = compositionElem.attribute( "paperHeight" ).toDouble( &heightConversionOk );
622  emit paperSizeChanged();
623  int numPages = compositionElem.attribute( "numPages", "1" ).toInt();
624 
625  QDomElement pageStyleSymbolElem = compositionElem.firstChildElement( "symbol" );
626  if ( !pageStyleSymbolElem.isNull() )
627  {
628  delete mPageStyleSymbol;
629  mPageStyleSymbol = dynamic_cast<QgsFillSymbolV2*>( QgsSymbolLayerV2Utils::loadSymbol( pageStyleSymbolElem ) );
630  }
631 
632  if ( widthConversionOk && heightConversionOk )
633  {
635  for ( int i = 0; i < numPages; ++i )
636  {
637  addPaperItem();
638  }
639  }
640 
641  //snapping
642  mSnapToGrid = compositionElem.attribute( "snapping", "0" ).toInt() == 0 ? false : true;
643  mGridVisible = compositionElem.attribute( "gridVisible", "0" ).toInt() == 0 ? false : true;
644 
645  mSnapGridResolution = compositionElem.attribute( "snapGridResolution" ).toDouble();
646  mSnapGridTolerance = compositionElem.attribute( "snapGridTolerance", "2.0" ).toDouble();
647  mSnapGridOffsetX = compositionElem.attribute( "snapGridOffsetX" ).toDouble();
648  mSnapGridOffsetY = compositionElem.attribute( "snapGridOffsetY" ).toDouble();
649 
650  mAlignmentSnap = compositionElem.attribute( "alignmentSnap", "1" ).toInt() == 0 ? false : true;
651  mGuidesVisible = compositionElem.attribute( "guidesVisible", "1" ).toInt() == 0 ? false : true;
652  mSmartGuides = compositionElem.attribute( "smartGuides", "1" ).toInt() == 0 ? false : true;
653  mAlignmentSnapTolerance = compositionElem.attribute( "alignmentSnapTolerance", "2.0" ).toDouble();
654 
655  //custom snap lines
656  QDomNodeList snapLineNodes = compositionElem.elementsByTagName( "SnapLine" );
657  for ( int i = 0; i < snapLineNodes.size(); ++i )
658  {
659  QDomElement snapLineElem = snapLineNodes.at( i ).toElement();
660  QGraphicsLineItem* snapItem = addSnapLine();
661  double x1 = snapLineElem.attribute( "x1" ).toDouble();
662  double y1 = snapLineElem.attribute( "y1" ).toDouble();
663  double x2 = snapLineElem.attribute( "x2" ).toDouble();
664  double y2 = snapLineElem.attribute( "y2" ).toDouble();
665  snapItem->setLine( x1, y1, x2, y2 );
666  }
667 
668  mPrintAsRaster = compositionElem.attribute( "printAsRaster" ).toInt();
669  mPrintResolution = compositionElem.attribute( "printResolution", "300" ).toInt();
670 
671  mGenerateWorldFile = compositionElem.attribute( "generateWorldFile", "0" ).toInt() == 1 ? true : false;
672 
674 
675  updateBounds();
676 
677  return true;
678 }
679 
680 bool QgsComposition::loadFromTemplate( const QDomDocument& doc, QMap<QString, QString>* substitutionMap, bool addUndoCommands )
681 {
683 
684  //delete all items and emit itemRemoved signal
685  QList<QGraphicsItem *> itemList = items();
686  QList<QGraphicsItem *>::iterator itemIter = itemList.begin();
687  for ( ; itemIter != itemList.end(); ++itemIter )
688  {
689  QgsComposerItem* cItem = dynamic_cast<QgsComposerItem*>( *itemIter );
690  if ( cItem )
691  {
692  removeItem( cItem );
693  emit itemRemoved( cItem );
694  delete cItem;
695  }
696  }
697  mItemZList.clear();
698 
699  mPages.clear();
700  mUndoStack.clear();
701 
702  QDomDocument importDoc;
703  if ( substitutionMap )
704  {
705  QString xmlString = doc.toString();
706  QMap<QString, QString>::const_iterator sIt = substitutionMap->constBegin();
707  for ( ; sIt != substitutionMap->constEnd(); ++sIt )
708  {
709  xmlString = xmlString.replace( "[" + sIt.key() + "]", encodeStringForXML( sIt.value() ) );
710  }
711 
712  QString errorMsg;
713  int errorLine, errorColumn;
714  if ( !importDoc.setContent( xmlString, &errorMsg, &errorLine, &errorColumn ) )
715  {
716  return false;
717  }
718  }
719  else
720  {
721  importDoc = doc;
722  }
723 
724  //read general settings
725  QDomElement compositionElem = importDoc.documentElement().firstChildElement( "Composition" );
726  if ( compositionElem.isNull() )
727  {
728  return false;
729  }
730 
731  bool ok = readXML( compositionElem, importDoc );
732  if ( !ok )
733  {
734  return false;
735  }
736 
737  // remove all uuid attributes since we don't want duplicates UUIDS
738  QDomNodeList composerItemsNodes = importDoc.elementsByTagName( "ComposerItem" );
739  for ( int i = 0; i < composerItemsNodes.count(); ++i )
740  {
741  QDomNode composerItemNode = composerItemsNodes.at( i );
742  if ( composerItemNode.isElement() )
743  {
744  composerItemNode.toElement().setAttribute( "templateUuid", composerItemNode.toElement().attribute( "uuid" ) );
745  composerItemNode.toElement().removeAttribute( "uuid" );
746  }
747  }
748 
749  //addItemsFromXML
750  addItemsFromXML( importDoc.documentElement(), importDoc, 0, addUndoCommands, 0 );
751 
752  // read atlas parameters
753  QDomElement atlasElem = importDoc.documentElement().firstChildElement( "Atlas" );
754  atlasComposition().readXML( atlasElem, importDoc );
755  return true;
756 }
757 
758 QPointF QgsComposition::minPointFromXml( const QDomElement& elem ) const
759 {
760  double minX = std::numeric_limits<double>::max();
761  double minY = std::numeric_limits<double>::max();
762  QDomNodeList composerItemList = elem.elementsByTagName( "ComposerItem" );
763  for ( int i = 0; i < composerItemList.size(); ++i )
764  {
765  QDomElement currentComposerItemElem = composerItemList.at( i ).toElement();
766  double x, y;
767  bool xOk, yOk;
768  x = currentComposerItemElem.attribute( "x" ).toDouble( &xOk );
769  y = currentComposerItemElem.attribute( "y" ).toDouble( &yOk );
770  if ( !xOk || !yOk )
771  {
772  continue;
773  }
774  minX = qMin( minX, x );
775  minY = qMin( minY, y );
776  }
777  if ( minX < std::numeric_limits<double>::max() )
778  {
779  return QPointF( minX, minY );
780  }
781  else
782  {
783  return QPointF( 0, 0 );
784  }
785 }
786 
787 void QgsComposition::addItemsFromXML( const QDomElement& elem, const QDomDocument& doc, QMap< QgsComposerMap*, int >* mapsToRestore,
788  bool addUndoCommands, QPointF* pos, bool pasteInPlace )
789 {
790  QPointF* pasteInPlacePt = 0;
791 
792  //if we are adding items to a composition which already contains items, we need to make sure
793  //these items are placed at the top of the composition and that zValues are not duplicated
794  //so, calculate an offset which needs to be added to the zValue of created items
795  int zOrderOffset = mItemZList.size();
796 
797  QPointF pasteShiftPos;
798  QgsComposerItem* lastPastedItem = 0;
799  if ( pos )
800  {
801  //If we are placing items relative to a certain point, then calculate how much we need
802  //to shift the items by so that they are placed at this point
803  //First, calculate the minimum position from the xml
804  QPointF minItemPos = minPointFromXml( elem );
805  //next, calculate how much each item needs to be shifted from its original position
806  //so that it's placed at the correct relative position
807  pasteShiftPos = *pos - minItemPos;
808 
809  //since we are pasting items, clear the existing selection
810  clearSelection();
811  }
812 
813  if ( pasteInPlace )
814  {
815  pasteInPlacePt = new QPointF( 0, pageNumberAt( *pos ) * ( mPageHeight + mSpaceBetweenPages ) );
816  }
817  QDomNodeList composerLabelList = elem.elementsByTagName( "ComposerLabel" );
818  for ( int i = 0; i < composerLabelList.size(); ++i )
819  {
820  QDomElement currentComposerLabelElem = composerLabelList.at( i ).toElement();
821  QgsComposerLabel* newLabel = new QgsComposerLabel( this );
822  newLabel->readXML( currentComposerLabelElem, doc );
823  if ( pos )
824  {
825  if ( pasteInPlacePt )
826  {
827  newLabel->setItemPosition( newLabel->pos().x(), fmod( newLabel->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
828  newLabel->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
829  }
830  else
831  {
832  newLabel->move( pasteShiftPos.x(), pasteShiftPos.y() );
833  }
834  newLabel->setSelected( true );
835  lastPastedItem = newLabel;
836  }
837  addComposerLabel( newLabel );
838  newLabel->setZValue( newLabel->zValue() + zOrderOffset );
839  if ( addUndoCommands )
840  {
841  pushAddRemoveCommand( newLabel, tr( "Label added" ) );
842  }
843  }
844  // map
845  QDomNodeList composerMapList = elem.elementsByTagName( "ComposerMap" );
846  for ( int i = 0; i < composerMapList.size(); ++i )
847  {
848  QDomElement currentComposerMapElem = composerMapList.at( i ).toElement();
849  QgsComposerMap* newMap = new QgsComposerMap( this );
850  newMap->readXML( currentComposerMapElem, doc );
851  newMap->assignFreeId();
852 
853  if ( mapsToRestore )
854  {
855  mapsToRestore->insert( newMap, ( int )( newMap->previewMode() ) );
857  }
858  addComposerMap( newMap, false );
859  newMap->setZValue( newMap->zValue() + zOrderOffset );
860  if ( pos )
861  {
862  if ( pasteInPlace )
863  {
864  newMap->setItemPosition( newMap->pos().x(), fmod( newMap->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
865  newMap->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
866  }
867  else
868  {
869  newMap->move( pasteShiftPos.x(), pasteShiftPos.y() );
870  }
871  newMap->setSelected( true );
872  lastPastedItem = newMap;
873  }
874 
875  if ( addUndoCommands )
876  {
877  pushAddRemoveCommand( newMap, tr( "Map added" ) );
878  }
879  }
880  //now that all map items have been created, re-connect overview map signals
881  QList<QgsComposerMap*> maps;
882  composerItems( maps );
883  for ( QList<QgsComposerMap*>::iterator mit = maps.begin(); mit != maps.end(); ++mit )
884  {
885  if (( *mit )->overviewFrameMapId() != -1 )
886  {
887  const QgsComposerMap* overviewMap = getComposerMapById(( *mit )->overviewFrameMapId() );
888  if ( overviewMap )
889  {
890  QObject::connect( overviewMap, SIGNAL( extentChanged() ), *mit, SLOT( overviewExtentChanged() ) );
891  }
892  }
893  }
894 
895  // arrow
896  QDomNodeList composerArrowList = elem.elementsByTagName( "ComposerArrow" );
897  for ( int i = 0; i < composerArrowList.size(); ++i )
898  {
899  QDomElement currentComposerArrowElem = composerArrowList.at( i ).toElement();
900  QgsComposerArrow* newArrow = new QgsComposerArrow( this );
901  newArrow->readXML( currentComposerArrowElem, doc );
902  if ( pos )
903  {
904  if ( pasteInPlace )
905  {
906  newArrow->setItemPosition( newArrow->pos().x(), fmod( newArrow->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
907  newArrow->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
908  }
909  else
910  {
911  newArrow->move( pasteShiftPos.x(), pasteShiftPos.y() );
912  }
913  newArrow->setSelected( true );
914  lastPastedItem = newArrow;
915  }
916  addComposerArrow( newArrow );
917  newArrow->setZValue( newArrow->zValue() + zOrderOffset );
918  if ( addUndoCommands )
919  {
920  pushAddRemoveCommand( newArrow, tr( "Arrow added" ) );
921  }
922  }
923  // scalebar
924  QDomNodeList composerScaleBarList = elem.elementsByTagName( "ComposerScaleBar" );
925  for ( int i = 0; i < composerScaleBarList.size(); ++i )
926  {
927  QDomElement currentComposerScaleBarElem = composerScaleBarList.at( i ).toElement();
928  QgsComposerScaleBar* newScaleBar = new QgsComposerScaleBar( this );
929  newScaleBar->readXML( currentComposerScaleBarElem, doc );
930  if ( pos )
931  {
932  if ( pasteInPlace )
933  {
934  newScaleBar->setItemPosition( newScaleBar->pos().x(), fmod( newScaleBar->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
935  newScaleBar->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
936  }
937  else
938  {
939  newScaleBar->move( pasteShiftPos.x(), pasteShiftPos.y() );
940  }
941  newScaleBar->setSelected( true );
942  lastPastedItem = newScaleBar;
943  }
944  addComposerScaleBar( newScaleBar );
945  newScaleBar->setZValue( newScaleBar->zValue() + zOrderOffset );
946  if ( addUndoCommands )
947  {
948  pushAddRemoveCommand( newScaleBar, tr( "Scale bar added" ) );
949  }
950  }
951  // shape
952  QDomNodeList composerShapeList = elem.elementsByTagName( "ComposerShape" );
953  for ( int i = 0; i < composerShapeList.size(); ++i )
954  {
955  QDomElement currentComposerShapeElem = composerShapeList.at( i ).toElement();
956  QgsComposerShape* newShape = new QgsComposerShape( this );
957  newShape->readXML( currentComposerShapeElem, doc );
958  //new shapes should default to symbol v2
959  newShape->setUseSymbolV2( true );
960  if ( pos )
961  {
962  if ( pasteInPlace )
963  {
964  newShape->setItemPosition( newShape->pos().x(), fmod( newShape->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
965  newShape->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
966  }
967  else
968  {
969  newShape->move( pasteShiftPos.x(), pasteShiftPos.y() );
970  }
971  newShape->setSelected( true );
972  lastPastedItem = newShape;
973  }
974  addComposerShape( newShape );
975  newShape->setZValue( newShape->zValue() + zOrderOffset );
976  if ( addUndoCommands )
977  {
978  pushAddRemoveCommand( newShape, tr( "Shape added" ) );
979  }
980  }
981  // picture
982  QDomNodeList composerPictureList = elem.elementsByTagName( "ComposerPicture" );
983  for ( int i = 0; i < composerPictureList.size(); ++i )
984  {
985  QDomElement currentComposerPictureElem = composerPictureList.at( i ).toElement();
986  QgsComposerPicture* newPicture = new QgsComposerPicture( this );
987  newPicture->readXML( currentComposerPictureElem, doc );
988  if ( pos )
989  {
990  if ( pasteInPlace )
991  {
992  newPicture->setItemPosition( newPicture->pos().x(), fmod( newPicture->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
993  newPicture->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
994  }
995  else
996  {
997  newPicture->move( pasteShiftPos.x(), pasteShiftPos.y() );
998  }
999  newPicture->setSelected( true );
1000  lastPastedItem = newPicture;
1001  }
1002  addComposerPicture( newPicture );
1003  newPicture->setZValue( newPicture->zValue() + zOrderOffset );
1004  if ( addUndoCommands )
1005  {
1006  pushAddRemoveCommand( newPicture, tr( "Picture added" ) );
1007  }
1008  }
1009  // legend
1010  QDomNodeList composerLegendList = elem.elementsByTagName( "ComposerLegend" );
1011  for ( int i = 0; i < composerLegendList.size(); ++i )
1012  {
1013  QDomElement currentComposerLegendElem = composerLegendList.at( i ).toElement();
1014  QgsComposerLegend* newLegend = new QgsComposerLegend( this );
1015  newLegend->readXML( currentComposerLegendElem, doc );
1016  if ( pos )
1017  {
1018  if ( pasteInPlace )
1019  {
1020  newLegend->setItemPosition( newLegend->pos().x(), fmod( newLegend->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
1021  newLegend->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
1022  }
1023  else
1024  {
1025  newLegend->move( pasteShiftPos.x(), pasteShiftPos.y() );
1026  }
1027  newLegend->setSelected( true );
1028  lastPastedItem = newLegend;
1029  }
1030  addComposerLegend( newLegend );
1031  newLegend->setZValue( newLegend->zValue() + zOrderOffset );
1032  if ( addUndoCommands )
1033  {
1034  pushAddRemoveCommand( newLegend, tr( "Legend added" ) );
1035  }
1036  }
1037  // table
1038  QDomNodeList composerTableList = elem.elementsByTagName( "ComposerAttributeTable" );
1039  for ( int i = 0; i < composerTableList.size(); ++i )
1040  {
1041  QDomElement currentComposerTableElem = composerTableList.at( i ).toElement();
1042  QgsComposerAttributeTable* newTable = new QgsComposerAttributeTable( this );
1043  newTable->readXML( currentComposerTableElem, doc );
1044  if ( pos )
1045  {
1046  if ( pasteInPlace )
1047  {
1048  newTable->setItemPosition( newTable->pos().x(), fmod( newTable->pos().y(), ( paperHeight() + spaceBetweenPages() ) ) );
1049  newTable->move( pasteInPlacePt->x(), pasteInPlacePt->y() );
1050  }
1051  else
1052  {
1053  newTable->move( pasteShiftPos.x(), pasteShiftPos.y() );
1054  }
1055  newTable->setSelected( true );
1056  lastPastedItem = newTable;
1057  }
1058  addComposerTable( newTable );
1059  newTable->setZValue( newTable->zValue() + zOrderOffset );
1060  if ( addUndoCommands )
1061  {
1062  pushAddRemoveCommand( newTable, tr( "Table added" ) );
1063  }
1064  }
1065  // html
1066  //TODO - fix this. pasting html items has no effect
1067  QDomNodeList composerHtmlList = elem.elementsByTagName( "ComposerHtml" );
1068  for ( int i = 0; i < composerHtmlList.size(); ++i )
1069  {
1070  QDomElement currentHtmlElem = composerHtmlList.at( i ).toElement();
1071  QgsComposerHtml* newHtml = new QgsComposerHtml( this, false );
1072  newHtml->readXML( currentHtmlElem, doc );
1073  newHtml->setCreateUndoCommands( true );
1074  this->addMultiFrame( newHtml );
1075 
1076  //offset z values for frames
1077  //TODO - fix this after fixing html item paste
1078  /*for ( int frameIdx = 0; frameIdx < newHtml->frameCount(); ++frameIdx )
1079  {
1080  QgsComposerFrame * frame = newHtml->frame( frameIdx );
1081  frame->setZValue( frame->zValue() + zOrderOffset );
1082  }*/
1083  }
1084 
1085  // groups (must be last as it references uuids of above items)
1086  //TODO - pasted groups lose group properties, since the uuids of group items
1087  //changes
1088  QDomNodeList groupList = elem.elementsByTagName( "ComposerItemGroup" );
1089  for ( int i = 0; i < groupList.size(); ++i )
1090  {
1091  QDomElement groupElem = groupList.at( i ).toElement();
1092  QgsComposerItemGroup *newGroup = new QgsComposerItemGroup( this );
1093  newGroup->readXML( groupElem, doc );
1094  addItem( newGroup );
1095  }
1096 
1097  //Since this function adds items grouped by type, and each item is added to end of
1098  //z order list in turn, it will now be inconsistent with the actual order of items in the scene.
1099  //Make sure z order list matches the actual order of items in the scene.
1100  refreshZList();
1101 
1102  if ( lastPastedItem )
1103  {
1104  emit selectedItemChanged( lastPastedItem );
1105  }
1106 
1107  delete pasteInPlacePt;
1108  pasteInPlacePt = 0;
1109 
1110 }
1111 
1113 {
1114  if ( !item )
1115  {
1116  return;
1117  }
1118  mItemZList.push_back( item );
1119  item->setZValue( mItemZList.size() );
1120 }
1121 
1123 {
1124  if ( !item )
1125  {
1126  return;
1127  }
1128  mItemZList.removeAll( item );
1129 }
1130 
1132 {
1133  QList<QgsComposerItem*> selectedItems = selectedComposerItems();
1134  QList<QgsComposerItem*>::iterator it = selectedItems.begin();
1135  for ( ; it != selectedItems.end(); ++it )
1136  {
1137  raiseItem( *it );
1138  }
1139 
1140  //update all positions
1141  updateZValues();
1142  update();
1143 }
1144 
1146 {
1147  //search item
1148  QMutableLinkedListIterator<QgsComposerItem*> it( mItemZList );
1149  if ( it.findNext( item ) )
1150  {
1151  if ( it.hasNext() )
1152  {
1153  it.remove();
1154  it.next();
1155  it.insert( item );
1156  }
1157  }
1158 }
1159 
1161 {
1162  //search item z list for selected item
1163  QLinkedListIterator<QgsComposerItem*> it( mItemZList );
1164  if ( it.findNext( item ) )
1165  {
1166  //return next item (list is sorted from lowest->highest items)
1167  if ( it.hasNext() )
1168  {
1169  return it.next();
1170  }
1171  }
1172  return 0;
1173 }
1174 
1176 {
1177  //search item z list for selected item
1178  QLinkedListIterator<QgsComposerItem*> it( mItemZList );
1179  if ( it.findNext( item ) )
1180  {
1181  //move position to before selected item
1182  it.previous();
1183  //now find previous item, since list is sorted from lowest->highest items
1184  if ( it.hasPrevious() )
1185  {
1186  return it.previous();
1187  }
1188  }
1189  return 0;
1190 }
1191 
1193 {
1194  QgsComposerItem* previousSelectedItem = 0;
1195  QList<QgsComposerItem*> selectedItems = selectedComposerItems();
1196  if ( selectedItems.size() > 0 )
1197  {
1198  previousSelectedItem = selectedItems.at( 0 );
1199  }
1200 
1201  if ( !previousSelectedItem )
1202  {
1203  return;
1204  }
1205 
1206  //select item with target z value
1207  QgsComposerItem* selectedItem = 0;
1208  switch ( direction )
1209  {
1211  selectedItem = getComposerItemBelow( previousSelectedItem );
1212  break;
1214  selectedItem = getComposerItemAbove( previousSelectedItem );
1215  break;
1216  }
1217 
1218  if ( !selectedItem )
1219  {
1220  return;
1221  }
1222 
1223  //ok, found a good target item
1224  clearSelection();
1225  selectedItem->setSelected( true );
1226  emit selectedItemChanged( selectedItem );
1227 }
1228 
1230 {
1231  QList<QgsComposerItem*> selectedItems = selectedComposerItems();
1232  QList<QgsComposerItem*>::iterator it = selectedItems.begin();
1233  for ( ; it != selectedItems.end(); ++it )
1234  {
1235  lowerItem( *it );
1236  }
1237 
1238  //update all positions
1239  updateZValues();
1240  update();
1241 }
1242 
1244 {
1245  //search item
1246  QMutableLinkedListIterator<QgsComposerItem*> it( mItemZList );
1247  if ( it.findNext( item ) )
1248  {
1249  it.previous();
1250  if ( it.hasPrevious() )
1251  {
1252  it.remove();
1253  it.previous();
1254  it.insert( item );
1255  }
1256  }
1257 }
1258 
1260 {
1261  QList<QgsComposerItem*> selectedItems = selectedComposerItems();
1262  QList<QgsComposerItem*>::iterator it = selectedItems.begin();
1263 
1264  for ( ; it != selectedItems.end(); ++it )
1265  {
1266  moveItemToTop( *it );
1267  }
1268 
1269  //update all positions
1270  updateZValues();
1271  update();
1272 }
1273 
1275 {
1276  //search item
1277  QMutableLinkedListIterator<QgsComposerItem*> it( mItemZList );
1278  if ( it.findNext( item ) )
1279  {
1280  it.remove();
1281  }
1282  mItemZList.push_back( item );
1283 }
1284 
1286 {
1287  QList<QgsComposerItem*> selectedItems = selectedComposerItems();
1288  QList<QgsComposerItem*>::iterator it = selectedItems.begin();
1289  for ( ; it != selectedItems.end(); ++it )
1290  {
1291  moveItemToBottom( *it );
1292  }
1293 
1294  //update all positions
1295  updateZValues();
1296  update();
1297 }
1298 
1300 {
1301  //search item
1302  QMutableLinkedListIterator<QgsComposerItem*> it( mItemZList );
1303  if ( it.findNext( item ) )
1304  {
1305  it.remove();
1306  }
1307  mItemZList.push_front( item );
1308 }
1309 
1311 {
1312  QList<QgsComposerItem*> selectedItems = selectedComposerItems();
1313  if ( selectedItems.size() < 2 )
1314  {
1315  return;
1316  }
1317 
1318  QRectF selectedItemBBox;
1319  if ( boundingRectOfSelectedItems( selectedItemBBox ) != 0 )
1320  {
1321  return;
1322  }
1323 
1324  double minXCoordinate = selectedItemBBox.left();
1325 
1326  //align items left to minimum x coordinate
1327  QUndoCommand* parentCommand = new QUndoCommand( tr( "Aligned items left" ) );
1328  QList<QgsComposerItem*>::iterator align_it = selectedItems.begin();
1329  for ( ; align_it != selectedItems.end(); ++align_it )
1330  {
1331  QgsComposerItemCommand* subcommand = new QgsComposerItemCommand( *align_it, "", parentCommand );
1332  subcommand->savePreviousState();
1333  ( *align_it )->setPos( minXCoordinate, ( *align_it )->pos().y() );
1334  subcommand->saveAfterState();
1335  }
1336  mUndoStack.push( parentCommand );
1337 }
1338 
1340 {
1341  QList<QgsComposerItem*> selectedItems = selectedComposerItems();
1342  if ( selectedItems.size() < 2 )
1343  {
1344  return;
1345  }
1346 
1347  QRectF selectedItemBBox;
1348  if ( boundingRectOfSelectedItems( selectedItemBBox ) != 0 )
1349  {
1350  return;
1351  }
1352 
1353  double averageXCoord = ( selectedItemBBox.left() + selectedItemBBox.right() ) / 2.0;
1354 
1355  //place items
1356  QUndoCommand* parentCommand = new QUndoCommand( tr( "Aligned items horizontal center" ) );
1357  QList<QgsComposerItem*>::iterator align_it = selectedItems.begin();
1358  for ( ; align_it != selectedItems.end(); ++align_it )
1359  {
1360  QgsComposerItemCommand* subcommand = new QgsComposerItemCommand( *align_it, "", parentCommand );
1361  subcommand->savePreviousState();
1362  ( *align_it )->setPos( averageXCoord - ( *align_it )->rect().width() / 2.0, ( *align_it )->pos().y() );
1363  subcommand->saveAfterState();
1364  }
1365  mUndoStack.push( parentCommand );
1366 }
1367 
1369 {
1370  QList<QgsComposerItem*> selectedItems = selectedComposerItems();
1371  if ( selectedItems.size() < 2 )
1372  {
1373  return;
1374  }
1375 
1376  QRectF selectedItemBBox;
1377  if ( boundingRectOfSelectedItems( selectedItemBBox ) != 0 )
1378  {
1379  return;
1380  }
1381 
1382  double maxXCoordinate = selectedItemBBox.right();
1383 
1384  //align items right to maximum x coordinate
1385  QUndoCommand* parentCommand = new QUndoCommand( tr( "Aligned items right" ) );
1386  QList<QgsComposerItem*>::iterator align_it = selectedItems.begin();
1387  for ( ; align_it != selectedItems.end(); ++align_it )
1388  {
1389  QgsComposerItemCommand* subcommand = new QgsComposerItemCommand( *align_it, "", parentCommand );
1390  subcommand->savePreviousState();
1391  ( *align_it )->setPos( maxXCoordinate - ( *align_it )->rect().width(), ( *align_it )->pos().y() );
1392  subcommand->saveAfterState();
1393  }
1394  mUndoStack.push( parentCommand );
1395 }
1396 
1398 {
1399  QList<QgsComposerItem*> selectedItems = selectedComposerItems();
1400  if ( selectedItems.size() < 2 )
1401  {
1402  return;
1403  }
1404 
1405  QRectF selectedItemBBox;
1406  if ( boundingRectOfSelectedItems( selectedItemBBox ) != 0 )
1407  {
1408  return;
1409  }
1410 
1411  double minYCoordinate = selectedItemBBox.top();
1412 
1413  QUndoCommand* parentCommand = new QUndoCommand( tr( "Aligned items top" ) );
1414  QList<QgsComposerItem*>::iterator align_it = selectedItems.begin();
1415  for ( ; align_it != selectedItems.end(); ++align_it )
1416  {
1417  QgsComposerItemCommand* subcommand = new QgsComposerItemCommand( *align_it, "", parentCommand );
1418  subcommand->savePreviousState();
1419  ( *align_it )->setPos(( *align_it )->pos().x(), minYCoordinate );
1420  subcommand->saveAfterState();
1421  }
1422  mUndoStack.push( parentCommand );
1423 }
1424 
1426 {
1427  QList<QgsComposerItem*> selectedItems = selectedComposerItems();
1428  if ( selectedItems.size() < 2 )
1429  {
1430  return;
1431  }
1432 
1433  QRectF selectedItemBBox;
1434  if ( boundingRectOfSelectedItems( selectedItemBBox ) != 0 )
1435  {
1436  return;
1437  }
1438 
1439  double averageYCoord = ( selectedItemBBox.top() + selectedItemBBox.bottom() ) / 2.0;
1440  QUndoCommand* parentCommand = new QUndoCommand( tr( "Aligned items vertical center" ) );
1441  QList<QgsComposerItem*>::iterator align_it = selectedItems.begin();
1442  for ( ; align_it != selectedItems.end(); ++align_it )
1443  {
1444  QgsComposerItemCommand* subcommand = new QgsComposerItemCommand( *align_it, "", parentCommand );
1445  subcommand->savePreviousState();
1446  ( *align_it )->setPos(( *align_it )->pos().x(), averageYCoord - ( *align_it )->rect().height() / 2 );
1447  subcommand->saveAfterState();
1448  }
1449  mUndoStack.push( parentCommand );
1450 }
1451 
1453 {
1454  QList<QgsComposerItem*> selectedItems = selectedComposerItems();
1455  if ( selectedItems.size() < 2 )
1456  {
1457  return;
1458  }
1459 
1460  QRectF selectedItemBBox;
1461  if ( boundingRectOfSelectedItems( selectedItemBBox ) != 0 )
1462  {
1463  return;
1464  }
1465 
1466  double maxYCoord = selectedItemBBox.bottom();
1467  QUndoCommand* parentCommand = new QUndoCommand( tr( "Aligned items bottom" ) );
1468  QList<QgsComposerItem*>::iterator align_it = selectedItems.begin();
1469  for ( ; align_it != selectedItems.end(); ++align_it )
1470  {
1471  QgsComposerItemCommand* subcommand = new QgsComposerItemCommand( *align_it, "", parentCommand );
1472  subcommand->savePreviousState();
1473  ( *align_it )->setPos(( *align_it )->pos().x(), maxYCoord - ( *align_it )->rect().height() );
1474  subcommand->saveAfterState();
1475  }
1476  mUndoStack.push( parentCommand );
1477 }
1478 
1480 {
1481  QUndoCommand* parentCommand = new QUndoCommand( tr( "Items locked" ) );
1482  QList<QgsComposerItem*> selectionList = selectedComposerItems();
1483  QList<QgsComposerItem*>::iterator itemIter = selectionList.begin();
1484  for ( ; itemIter != selectionList.end(); ++itemIter )
1485  {
1486  QgsComposerItemCommand* subcommand = new QgsComposerItemCommand( *itemIter, "", parentCommand );
1487  subcommand->savePreviousState();
1488  ( *itemIter )->setPositionLock( true );
1489  subcommand->saveAfterState();
1490  }
1491 
1492  clearSelection();
1493  mUndoStack.push( parentCommand );
1494 }
1495 
1497 {
1498  //unlock all items in composer
1499 
1500  QUndoCommand* parentCommand = new QUndoCommand( tr( "Items unlocked" ) );
1501 
1502  //first, clear the selection
1503  clearSelection();
1504 
1505  QList<QGraphicsItem *> itemList = items();
1506  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
1507  for ( ; itemIt != itemList.end(); ++itemIt )
1508  {
1509  QgsComposerItem* mypItem = dynamic_cast<QgsComposerItem *>( *itemIt );
1510  if ( mypItem && mypItem->positionLock() )
1511  {
1512  QgsComposerItemCommand* subcommand = new QgsComposerItemCommand( mypItem, "", parentCommand );
1513  subcommand->savePreviousState();
1514  mypItem->setPositionLock( false );
1515  //select unlocked items, same behaviour as illustrator
1516  mypItem->setSelected( true );
1517  emit selectedItemChanged( mypItem );
1518  subcommand->saveAfterState();
1519  }
1520  }
1521  mUndoStack.push( parentCommand );
1522 }
1523 
1524 void QgsComposition::updateZValues( bool addUndoCommands )
1525 {
1526  int counter = 1;
1527  QLinkedList<QgsComposerItem*>::iterator it = mItemZList.begin();
1528  QgsComposerItem* currentItem = 0;
1529 
1530  QUndoCommand* parentCommand = 0;
1531  if ( addUndoCommands )
1532  {
1533  parentCommand = new QUndoCommand( tr( "Item z-order changed" ) );
1534  }
1535  for ( ; it != mItemZList.end(); ++it )
1536  {
1537  currentItem = *it;
1538  if ( currentItem )
1539  {
1540  QgsComposerItemCommand* subcommand = 0;
1541  if ( addUndoCommands )
1542  {
1543  subcommand = new QgsComposerItemCommand( *it, "", parentCommand );
1544  subcommand->savePreviousState();
1545  }
1546  currentItem->setZValue( counter );
1547  if ( addUndoCommands )
1548  {
1549  subcommand->saveAfterState();
1550  }
1551  }
1552  ++counter;
1553  }
1554  if ( addUndoCommands )
1555  {
1556  mUndoStack.push( parentCommand );
1557  }
1558 }
1559 
1561 {
1562  if ( mItemZList.size() < 2 )
1563  {
1564  return;
1565  }
1566 
1567  QLinkedList<QgsComposerItem*>::const_iterator lIt = mItemZList.constBegin();
1568  QLinkedList<QgsComposerItem*> sortedList;
1569 
1570  for ( ; lIt != mItemZList.constEnd(); ++lIt )
1571  {
1572  QLinkedList<QgsComposerItem*>::iterator insertIt = sortedList.begin();
1573  for ( ; insertIt != sortedList.end(); ++insertIt )
1574  {
1575  if (( *lIt )->zValue() < ( *insertIt )->zValue() )
1576  {
1577  break;
1578  }
1579  }
1580  sortedList.insert( insertIt, ( *lIt ) );
1581  }
1582 
1583  mItemZList = sortedList;
1584 }
1585 
1587 {
1588  QLinkedList<QgsComposerItem*> sortedList;
1589 
1590  //rebuild the item z order list based on the current zValues of items in the scene
1591 
1592  //get items in descending zValue order
1593  QList<QGraphicsItem*> itemList = items();
1594  QList<QGraphicsItem*>::iterator itemIt = itemList.begin();
1595  for ( ; itemIt != itemList.end(); ++itemIt )
1596  {
1597  QgsComposerItem* composerItem = dynamic_cast<QgsComposerItem*>( *itemIt );
1598  if ( composerItem )
1599  {
1600  if ( composerItem->type() != QgsComposerItem::ComposerPaper && composerItem->type() != QgsComposerItem::ComposerFrame )
1601  {
1602  //since the z order list is in ascending zValue order (opposite order to itemList), we prepend each item
1603  sortedList.prepend( composerItem );
1604  }
1605  }
1606  }
1607 
1608  mItemZList = sortedList;
1609 
1610  //Finally, rebuild the zValue of all items to remove any duplicate zValues and make sure there's
1611  //no missing zValues.
1612  updateZValues( false );
1613 }
1614 
1615 QPointF QgsComposition::snapPointToGrid( const QPointF& scenePoint ) const
1616 {
1617  if ( !mSnapToGrid || mSnapGridResolution <= 0 )
1618  {
1619  return scenePoint;
1620  }
1621 
1622  //y offset to current page
1623  int pageNr = ( int )( scenePoint.y() / ( mPageHeight + mSpaceBetweenPages ) );
1624  double yOffset = pageNr * ( mPageHeight + mSpaceBetweenPages );
1625  double yPage = scenePoint.y() - yOffset; //y-coordinate relative to current page
1626 
1627  //snap x coordinate
1628  int xRatio = ( int )(( scenePoint.x() - mSnapGridOffsetX ) / mSnapGridResolution + 0.5 );
1629  int yRatio = ( int )(( yPage - mSnapGridOffsetY ) / mSnapGridResolution + 0.5 );
1630 
1631  double xSnapped = xRatio * mSnapGridResolution + mSnapGridOffsetX;
1632  double ySnapped = yRatio * mSnapGridResolution + mSnapGridOffsetY + yOffset;
1633 
1634  if ( abs( xSnapped - scenePoint.x() ) > mSnapGridTolerance )
1635  {
1636  //snap distance is outside of tolerance
1637  xSnapped = scenePoint.x();
1638  }
1639  if ( abs( ySnapped - scenePoint.y() ) > mSnapGridTolerance )
1640  {
1641  //snap distance is outside of tolerance
1642  ySnapped = scenePoint.y();
1643  }
1644 
1645  return QPointF( xSnapped, ySnapped );
1646 }
1647 
1648 QGraphicsLineItem* QgsComposition::addSnapLine()
1649 {
1650  QGraphicsLineItem* item = new QGraphicsLineItem();
1651  QPen linePen( Qt::SolidLine );
1652  linePen.setColor( Qt::red );
1653  // use a pen width of 0, since this activates a cosmetic pen
1654  // which doesn't scale with the composer and keeps a constant size
1655  linePen.setWidthF( 0 );
1656  item->setPen( linePen );
1657  item->setZValue( 100 );
1658  item->setVisible( mGuidesVisible );
1659  addItem( item );
1660  mSnapLines.push_back( item );
1661  return item;
1662 }
1663 
1664 void QgsComposition::removeSnapLine( QGraphicsLineItem* line )
1665 {
1666  removeItem( line );
1667  mSnapLines.removeAll( line );
1668  delete line;
1669 }
1670 
1672 {
1673  QList< QGraphicsLineItem* >::iterator it = mSnapLines.begin();
1674  for ( ; it != mSnapLines.end(); ++it )
1675  {
1676  removeItem(( *it ) );
1677  delete( *it );
1678  }
1679  mSnapLines.clear();
1680 }
1681 
1683 {
1684  mGuidesVisible = visible;
1685  QList< QGraphicsLineItem* >::iterator it = mSnapLines.begin();
1686  for ( ; it != mSnapLines.end(); ++it )
1687  {
1688  if ( visible )
1689  {
1690  ( *it )->show();
1691  }
1692  else
1693  {
1694  ( *it )->hide();
1695  }
1696  }
1697 }
1698 
1699 QGraphicsLineItem* QgsComposition::nearestSnapLine( bool horizontal, double x, double y, double tolerance,
1700  QList< QPair< QgsComposerItem*, QgsComposerItem::ItemPositionMode> >& snappedItems )
1701 {
1702  double minSqrDist = DBL_MAX;
1703  QGraphicsLineItem* item = 0;
1704  double currentXCoord = 0;
1705  double currentYCoord = 0;
1706  double currentSqrDist = 0;
1707  double sqrTolerance = tolerance * tolerance;
1708 
1709  snappedItems.clear();
1710 
1711  QList< QGraphicsLineItem* >::const_iterator it = mSnapLines.constBegin();
1712  for ( ; it != mSnapLines.constEnd(); ++it )
1713  {
1714  bool itemHorizontal = qgsDoubleNear(( *it )->line().y2() - ( *it )->line().y1(), 0 );
1715  if ( horizontal && itemHorizontal )
1716  {
1717  currentYCoord = ( *it )->line().y1();
1718  currentSqrDist = ( y - currentYCoord ) * ( y - currentYCoord );
1719  }
1720  else if ( !horizontal && !itemHorizontal )
1721  {
1722  currentXCoord = ( *it )->line().x1();
1723  currentSqrDist = ( x - currentXCoord ) * ( x - currentXCoord );
1724  }
1725  else
1726  {
1727  continue;
1728  }
1729 
1730  if ( currentSqrDist < minSqrDist && currentSqrDist < sqrTolerance )
1731  {
1732  item = *it;
1733  minSqrDist = currentSqrDist;
1734  }
1735  }
1736 
1737  double itemTolerance = 0.0000001;
1738  if ( item )
1739  {
1740  //go through all the items to find items snapped to this snap line
1741  QList<QGraphicsItem *> itemList = items();
1742  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
1743  for ( ; itemIt != itemList.end(); ++itemIt )
1744  {
1745  QgsComposerItem* currentItem = dynamic_cast<QgsComposerItem*>( *itemIt );
1746  if ( !currentItem || currentItem->type() == QgsComposerItem::ComposerPaper )
1747  {
1748  continue;
1749  }
1750 
1751  if ( horizontal )
1752  {
1753  if ( qgsDoubleNear( currentYCoord, currentItem->pos().y() + currentItem->rect().top(), itemTolerance ) )
1754  {
1755  snappedItems.append( qMakePair( currentItem, QgsComposerItem::UpperMiddle ) );
1756  }
1757  else if ( qgsDoubleNear( currentYCoord, currentItem->pos().y() + currentItem->rect().center().y(), itemTolerance ) )
1758  {
1759  snappedItems.append( qMakePair( currentItem, QgsComposerItem::Middle ) );
1760  }
1761  else if ( qgsDoubleNear( currentYCoord, currentItem->pos().y() + currentItem->rect().bottom(), itemTolerance ) )
1762  {
1763  snappedItems.append( qMakePair( currentItem, QgsComposerItem::LowerMiddle ) );
1764  }
1765  }
1766  else
1767  {
1768  if ( qgsDoubleNear( currentXCoord, currentItem->pos().x(), itemTolerance ) )
1769  {
1770  snappedItems.append( qMakePair( currentItem, QgsComposerItem::MiddleLeft ) );
1771  }
1772  else if ( qgsDoubleNear( currentXCoord, currentItem->pos().x() + currentItem->rect().center().x(), itemTolerance ) )
1773  {
1774  snappedItems.append( qMakePair( currentItem, QgsComposerItem::Middle ) );
1775  }
1776  else if ( qgsDoubleNear( currentXCoord, currentItem->pos().x() + currentItem->rect().width(), itemTolerance ) )
1777  {
1778  snappedItems.append( qMakePair( currentItem, QgsComposerItem::MiddleRight ) );
1779  }
1780  }
1781  }
1782  }
1783 
1784  return item;
1785 }
1786 
1788 {
1789  QList<QgsComposerItem*> selectedItems = selectedComposerItems();
1790  if ( selectedItems.size() < 1 )
1791  {
1792  return 1;
1793  }
1794 
1795  //set the box to the first item
1796  QgsComposerItem* currentItem = selectedItems.at( 0 );
1797  double minX = currentItem->pos().x();
1798  double minY = currentItem->pos().y();
1799  double maxX = minX + currentItem->rect().width();
1800  double maxY = minY + currentItem->rect().height();
1801 
1802  double currentMinX, currentMinY, currentMaxX, currentMaxY;
1803 
1804  for ( int i = 1; i < selectedItems.size(); ++i )
1805  {
1806  currentItem = selectedItems.at( i );
1807  currentMinX = currentItem->pos().x();
1808  currentMinY = currentItem->pos().y();
1809  currentMaxX = currentMinX + currentItem->rect().width();
1810  currentMaxY = currentMinY + currentItem->rect().height();
1811 
1812  if ( currentMinX < minX )
1813  minX = currentMinX;
1814  if ( currentMaxX > maxX )
1815  maxX = currentMaxX;
1816  if ( currentMinY < minY )
1817  minY = currentMinY;
1818  if ( currentMaxY > maxY )
1819  maxY = currentMaxY;
1820  }
1821 
1822  bRect.setTopLeft( QPointF( minX, minY ) );
1823  bRect.setBottomRight( QPointF( maxX, maxY ) );
1824  return 0;
1825 }
1826 
1828 {
1829  mSnapToGrid = b;
1830  updatePaperItems();
1831 }
1832 
1834 {
1835  mGridVisible = b;
1836  updatePaperItems();
1837 }
1838 
1840 {
1841  mSnapGridResolution = r;
1842  updatePaperItems();
1843 }
1844 
1846 {
1847  mSnapGridTolerance = tolerance;
1848 }
1849 
1851 {
1852  mSnapGridOffsetX = offset;
1853  updatePaperItems();
1854 }
1855 
1857 {
1858  mSnapGridOffsetY = offset;
1859  updatePaperItems();
1860 }
1861 
1862 void QgsComposition::setGridPen( const QPen& p )
1863 {
1864  mGridPen = p;
1865  //make sure grid is drawn using a zero-width cosmetic pen
1866  mGridPen.setWidthF( 0 );
1867  updatePaperItems();
1868 }
1869 
1871 {
1872  mGridStyle = s;
1873  updatePaperItems();
1874 }
1875 
1877 {
1878  //load new composer setting values
1879  loadSettings();
1880  //update any paper items to reflect new settings
1881  updatePaperItems();
1882 }
1883 
1885 {
1886  //read grid style, grid color and pen width from settings
1887  QSettings s;
1888 
1889  QString gridStyleString;
1890  gridStyleString = s.value( "/Composer/gridStyle", "Dots" ).toString();
1891 
1892  int gridRed, gridGreen, gridBlue, gridAlpha;
1893  gridRed = s.value( "/Composer/gridRed", 190 ).toInt();
1894  gridGreen = s.value( "/Composer/gridGreen", 190 ).toInt();
1895  gridBlue = s.value( "/Composer/gridBlue", 190 ).toInt();
1896  gridAlpha = s.value( "/Composer/gridAlpha", 100 ).toInt();
1897  QColor gridColor = QColor( gridRed, gridGreen, gridBlue, gridAlpha );
1898 
1899  mGridPen.setColor( gridColor );
1900  mGridPen.setWidthF( 0 );
1901 
1902  if ( gridStyleString == "Dots" )
1903  {
1904  mGridStyle = Dots;
1905  }
1906  else if ( gridStyleString == "Crosses" )
1907  {
1908  mGridStyle = Crosses;
1909  }
1910  else
1911  {
1912  mGridStyle = Solid;
1913  }
1914 }
1915 
1917 {
1918  delete mActiveItemCommand;
1919  if ( !item )
1920  {
1921  mActiveItemCommand = 0;
1922  return;
1923  }
1924 
1926  {
1927  mActiveItemCommand = new QgsComposerItemCommand( item, commandText );
1928  }
1929  else
1930  {
1931  mActiveItemCommand = new QgsComposerMergeCommand( c, item, commandText );
1932  }
1934 }
1935 
1937 {
1938  if ( mActiveItemCommand )
1939  {
1941  if ( mActiveItemCommand->containsChange() ) //protect against empty commands
1942  {
1944  QgsProject::instance()->dirty( true );
1945  }
1946  else
1947  {
1948  delete mActiveItemCommand;
1949  }
1950  mActiveItemCommand = 0;
1951  }
1952 }
1953 
1955 {
1956  delete mActiveItemCommand;
1957  mActiveItemCommand = 0;
1958 }
1959 
1960 void QgsComposition::beginMultiFrameCommand( QgsComposerMultiFrame* multiFrame, const QString& text )
1961 {
1962  delete mActiveMultiFrameCommand;
1963  mActiveMultiFrameCommand = new QgsComposerMultiFrameCommand( multiFrame, text );
1965 }
1966 
1968 {
1970  {
1973  {
1975  QgsProject::instance()->dirty( true );
1976  }
1977  else
1978  {
1979  delete mActiveMultiFrameCommand;
1980  }
1982  }
1983 }
1984 
1986 {
1987  mMultiFrames.insert( multiFrame );
1988 
1989  updateBounds();
1990 }
1991 
1993 {
1994  mMultiFrames.remove( multiFrame );
1995 
1996  updateBounds();
1997 }
1998 
2000 {
2001  addItem( arrow );
2002 
2003  updateBounds();
2004  connect( arrow, SIGNAL( sizeChanged() ), this, SLOT( updateBounds() ) );
2005 
2006  emit composerArrowAdded( arrow );
2007 }
2008 
2010 {
2011  addItem( label );
2012 
2013  updateBounds();
2014  connect( label, SIGNAL( sizeChanged() ), this, SLOT( updateBounds() ) );
2015 
2016  emit composerLabelAdded( label );
2017 }
2018 
2019 void QgsComposition::addComposerMap( QgsComposerMap* map, bool setDefaultPreviewStyle )
2020 {
2021  addItem( map );
2022  if ( setDefaultPreviewStyle )
2023  {
2024  //set default preview mode to cache. Must be done here between adding composer map to scene and emiting signal
2026  }
2027 
2028  if ( map->previewMode() != QgsComposerMap::Rectangle )
2029  {
2030  map->cache();
2031  }
2032 
2033  updateBounds();
2034  connect( map, SIGNAL( sizeChanged() ), this, SLOT( updateBounds() ) );
2035 
2036  emit composerMapAdded( map );
2037 }
2038 
2040 {
2041  addItem( scaleBar );
2042 
2043  updateBounds();
2044  connect( scaleBar, SIGNAL( sizeChanged() ), this, SLOT( updateBounds() ) );
2045 
2046  emit composerScaleBarAdded( scaleBar );
2047 }
2048 
2050 {
2051  addItem( legend );
2052 
2053  updateBounds();
2054  connect( legend, SIGNAL( sizeChanged() ), this, SLOT( updateBounds() ) );
2055 
2056  emit composerLegendAdded( legend );
2057 }
2058 
2060 {
2061  addItem( picture );
2062 
2063  updateBounds();
2064  connect( picture, SIGNAL( sizeChanged() ), this, SLOT( updateBounds() ) );
2065 
2066  emit composerPictureAdded( picture );
2067 }
2068 
2070 {
2071  addItem( shape );
2072 
2073  updateBounds();
2074  connect( shape, SIGNAL( sizeChanged() ), this, SLOT( updateBounds() ) );
2075 
2076  emit composerShapeAdded( shape );
2077 }
2078 
2080 {
2081  addItem( table );
2082 
2083  updateBounds();
2084  connect( table, SIGNAL( sizeChanged() ), this, SLOT( updateBounds() ) );
2085 
2086  emit composerTableAdded( table );
2087 }
2088 
2090 {
2091  addItem( frame );
2092 
2093  updateBounds();
2094  connect( frame, SIGNAL( sizeChanged() ), this, SLOT( updateBounds() ) );
2095 
2096  emit composerHtmlFrameAdded( html, frame );
2097 }
2098 
2099 void QgsComposition::removeComposerItem( QgsComposerItem* item, bool createCommand )
2100 {
2101  QgsComposerMap* map = dynamic_cast<QgsComposerMap *>( item );
2102 
2103  if ( !map || !map->isDrawing() ) //don't delete a composer map while it draws
2104  {
2105  removeItem( item );
2106  QgsComposerItemGroup* itemGroup = dynamic_cast<QgsComposerItemGroup*>( item );
2107  if ( itemGroup )
2108  {
2109  //add add/remove item command for every item in the group
2110  QUndoCommand* parentCommand = new QUndoCommand( tr( "Remove item group" ) );
2111 
2112  QSet<QgsComposerItem*> groupedItems = itemGroup->items();
2113  QSet<QgsComposerItem*>::iterator it = groupedItems.begin();
2114  for ( ; it != groupedItems.end(); ++it )
2115  {
2116  QgsAddRemoveItemCommand* subcommand = new QgsAddRemoveItemCommand( QgsAddRemoveItemCommand::Removed, *it, this, "", parentCommand );
2117  connectAddRemoveCommandSignals( subcommand );
2118  emit itemRemoved( *it );
2119  }
2120 
2121  undoStack()->push( parentCommand );
2122  emit itemRemoved( itemGroup );
2123  delete itemGroup;
2124  }
2125  else
2126  {
2127  bool frameItem = ( item->type() == QgsComposerItem::ComposerFrame );
2128  QgsComposerMultiFrame* multiFrame = 0;
2129  if ( createCommand )
2130  {
2131  if ( frameItem ) //multiframe tracks item changes
2132  {
2133  multiFrame = static_cast<QgsComposerFrame*>( item )->multiFrame();
2134  item->beginItemCommand( tr( "Frame deleted" ) );
2135  emit itemRemoved( item );
2136  item->endItemCommand();
2137  }
2138  else
2139  {
2140  emit itemRemoved( item );
2141  pushAddRemoveCommand( item, tr( "Item deleted" ), QgsAddRemoveItemCommand::Removed );
2142  }
2143  }
2144  else
2145  {
2146  emit itemRemoved( item );
2147  }
2148 
2149  //check if there are frames left. If not, remove the multi frame
2150  if ( frameItem && multiFrame )
2151  {
2152  if ( multiFrame->frameCount() < 1 )
2153  {
2154  removeMultiFrame( multiFrame );
2155  if ( createCommand )
2156  {
2158  multiFrame, this, tr( "Multiframe removed" ) );
2159  undoStack()->push( command );
2160  }
2161  else
2162  {
2163  delete multiFrame;
2164  }
2165  }
2166  }
2167  }
2168  }
2169 
2170  updateBounds();
2171 }
2172 
2174 {
2175  QgsAddRemoveItemCommand* c = new QgsAddRemoveItemCommand( state, item, this, text );
2177  undoStack()->push( c );
2178  QgsProject::instance()->dirty( true );
2179 }
2180 
2182 {
2183  if ( !c )
2184  {
2185  return;
2186  }
2187 
2188  QObject::connect( c, SIGNAL( itemRemoved( QgsComposerItem* ) ), this, SIGNAL( itemRemoved( QgsComposerItem* ) ) );
2189  QObject::connect( c, SIGNAL( itemAdded( QgsComposerItem* ) ), this, SLOT( sendItemAddedSignal( QgsComposerItem* ) ) );
2190 }
2191 
2193 {
2194  //cast and send proper signal
2195  item->setSelected( true );
2196  QgsComposerArrow* arrow = dynamic_cast<QgsComposerArrow*>( item );
2197  if ( arrow )
2198  {
2199  emit composerArrowAdded( arrow );
2200  emit selectedItemChanged( arrow );
2201  return;
2202  }
2203  QgsComposerLabel* label = dynamic_cast<QgsComposerLabel*>( item );
2204  if ( label )
2205  {
2206  emit composerLabelAdded( label );
2207  emit selectedItemChanged( label );
2208  return;
2209  }
2210  QgsComposerMap* map = dynamic_cast<QgsComposerMap*>( item );
2211  if ( map )
2212  {
2213  emit composerMapAdded( map );
2214  emit selectedItemChanged( map );
2215  return;
2216  }
2217  QgsComposerScaleBar* scalebar = dynamic_cast<QgsComposerScaleBar*>( item );
2218  if ( scalebar )
2219  {
2220  emit composerScaleBarAdded( scalebar );
2221  emit selectedItemChanged( scalebar );
2222  return;
2223  }
2224  QgsComposerLegend* legend = dynamic_cast<QgsComposerLegend*>( item );
2225  if ( legend )
2226  {
2227  emit composerLegendAdded( legend );
2228  emit selectedItemChanged( legend );
2229  return;
2230  }
2231  QgsComposerPicture* picture = dynamic_cast<QgsComposerPicture*>( item );
2232  if ( picture )
2233  {
2234  emit composerPictureAdded( picture );
2235  emit selectedItemChanged( picture );
2236  return;
2237  }
2238  QgsComposerShape* shape = dynamic_cast<QgsComposerShape*>( item );
2239  if ( shape )
2240  {
2241  emit composerShapeAdded( shape );
2242  emit selectedItemChanged( shape );
2243  return;
2244  }
2245  QgsComposerAttributeTable* table = dynamic_cast<QgsComposerAttributeTable*>( item );
2246  if ( table )
2247  {
2248  emit composerTableAdded( table );
2249  emit selectedItemChanged( table );
2250  return;
2251  }
2252  QgsComposerFrame* frame = dynamic_cast<QgsComposerFrame*>( item );
2253  if ( frame )
2254  {
2255  //emit composerFrameAdded( multiframe, frame, );
2256  QgsComposerMultiFrame* mf = frame->multiFrame();
2257  QgsComposerHtml* html = dynamic_cast<QgsComposerHtml*>( mf );
2258  if ( html )
2259  {
2260  emit composerHtmlFrameAdded( html, frame );
2261  }
2262  emit selectedItemChanged( frame );
2263  return;
2264  }
2265 }
2266 
2268 {
2269  QList< QgsPaperItem* >::iterator paperIt = mPages.begin();
2270  for ( ; paperIt != mPages.end(); ++paperIt )
2271  {
2272  ( *paperIt )->update();
2273  }
2274 }
2275 
2277 {
2278  double paperHeight = this->paperHeight();
2279  double paperWidth = this->paperWidth();
2280  double currentY = paperHeight * mPages.size() + mPages.size() * mSpaceBetweenPages; //add 10mm visible space between pages
2281  QgsPaperItem* paperItem = new QgsPaperItem( 0, currentY, paperWidth, paperHeight, this ); //default size A4
2282  paperItem->setBrush( Qt::white );
2283  addItem( paperItem );
2284  paperItem->setZValue( 0 );
2285  mPages.push_back( paperItem );
2286 
2287  QgsExpression::setSpecialColumn( "$numpages", QVariant(( int )mPages.size() ) );
2288 }
2289 
2291 {
2292  for ( int i = 0; i < mPages.size(); ++i )
2293  {
2294  delete mPages.at( i );
2295  }
2296  mPages.clear();
2297  QgsExpression::setSpecialColumn( "$numpages", QVariant(( int )0 ) );
2298 }
2299 
2301 {
2302  QSet<QgsComposerMultiFrame*>::iterator multiFrameIt = mMultiFrames.begin();
2303  for ( ; multiFrameIt != mMultiFrames.end(); ++multiFrameIt )
2304  {
2305  delete *multiFrameIt;
2306  }
2307  mMultiFrames.clear();
2308 }
2309 
2310 void QgsComposition::beginPrintAsPDF( QPrinter& printer, const QString& file )
2311 {
2312  printer.setOutputFormat( QPrinter::PdfFormat );
2313  printer.setOutputFileName( file );
2314  printer.setPaperSize( QSizeF( paperWidth(), paperHeight() ), QPrinter::Millimeter );
2315 
2316  QgsPaintEngineHack::fixEngineFlags( printer.paintEngine() );
2317 }
2318 
2319 void QgsComposition::exportAsPDF( const QString& file )
2320 {
2321  QPrinter printer;
2322  beginPrintAsPDF( printer, file );
2323  print( printer );
2324 }
2325 
2326 void QgsComposition::doPrint( QPrinter& printer, QPainter& p )
2327 {
2328 //QgsComposition starts page numbering at 0
2329  int fromPage = ( printer.fromPage() < 1 ) ? 0 : printer.fromPage() - 1 ;
2330  int toPage = ( printer.toPage() < 1 ) ? numPages() - 1 : printer.toPage() - 1;
2331 
2332  if ( mPrintAsRaster )
2333  {
2334  for ( int i = fromPage; i <= toPage; ++i )
2335  {
2336  if ( i > fromPage )
2337  {
2338  printer.newPage();
2339  }
2340 
2341  QImage image = printPageAsRaster( i );
2342  if ( !image.isNull() )
2343  {
2344  QRectF targetArea( 0, 0, image.width(), image.height() );
2345  p.drawImage( targetArea, image, targetArea );
2346  }
2347  }
2348  }
2349 
2350  if ( !mPrintAsRaster )
2351  {
2352  for ( int i = fromPage; i <= toPage; ++i )
2353  {
2354  if ( i > fromPage )
2355  {
2356  printer.newPage();
2357  }
2358  renderPage( &p, i );
2359  }
2360  }
2361 }
2362 
2363 void QgsComposition::beginPrint( QPrinter &printer )
2364 {
2365  //set resolution based on composer setting
2366  printer.setFullPage( true );
2367  printer.setColorMode( QPrinter::Color );
2368 
2369  //set user-defined resolution
2370  printer.setResolution( printResolution() );
2371 }
2372 
2373 void QgsComposition::print( QPrinter &printer )
2374 {
2375  beginPrint( printer );
2376  QPainter p( &printer );
2377  doPrint( printer, p );
2378 }
2379 
2381 {
2382  //print out via QImage, code copied from on_mActionExportAsImage_activated
2383  int width = ( int )( printResolution() * paperWidth() / 25.4 );
2384  int height = ( int )( printResolution() * paperHeight() / 25.4 );
2385  QImage image( QSize( width, height ), QImage::Format_ARGB32 );
2386  if ( !image.isNull() )
2387  {
2388  image.setDotsPerMeterX( printResolution() / 25.4 * 1000 );
2389  image.setDotsPerMeterY( printResolution() / 25.4 * 1000 );
2390  image.fill( 0 );
2391  QPainter imagePainter( &image );
2392  renderPage( &imagePainter, page );
2393  if ( !imagePainter.isActive() ) return QImage();
2394  }
2395  return image;
2396 }
2397 
2398 void QgsComposition::renderPage( QPainter* p, int page )
2399 {
2400  if ( mPages.size() <= page )
2401  {
2402  return;
2403  }
2404 
2405  QgsPaperItem* paperItem = mPages[page];
2406  if ( !paperItem )
2407  {
2408  return;
2409  }
2410 
2411  QPaintDevice* paintDevice = p->device();
2412  if ( !paintDevice )
2413  {
2414  return;
2415  }
2416 
2417  QRectF paperRect = QRectF( paperItem->pos().x(), paperItem->pos().y(), paperItem->rect().width(), paperItem->rect().height() );
2418 
2419  QgsComposition::PlotStyle savedPlotStyle = mPlotStyle;
2421 
2422  setSnapLinesVisible( false );
2423  //hide background before rendering
2424  setBackgroundBrush( Qt::NoBrush );
2425  render( p, QRectF( 0, 0, paintDevice->width(), paintDevice->height() ), paperRect );
2426  //show background after rendering
2427  setBackgroundBrush( QColor( 215, 215, 215 ) );
2428  setSnapLinesVisible( true );
2429 
2430  mPlotStyle = savedPlotStyle;
2431 }
2432 
2433 QString QgsComposition::encodeStringForXML( const QString& str )
2434 {
2435  QString modifiedStr( str );
2436  modifiedStr.replace( "&", "&amp;" );
2437  modifiedStr.replace( "\"", "&quot;" );
2438  modifiedStr.replace( "'", "&apos;" );
2439  modifiedStr.replace( "<", "&lt;" );
2440  modifiedStr.replace( ">", "&gt;" );
2441  return modifiedStr;
2442 }
2443 
2444 void QgsComposition::computeWorldFileParameters( double& a, double& b, double& c, double& d, double& e, double& f ) const
2445 {
2446  //
2447  // Word file parameters : affine transformation parameters from pixel coordinates to map coordinates
2448 
2449  if ( !mWorldFileMap )
2450  {
2451  return;
2452  }
2453 
2454  QRectF brect = mWorldFileMap->mapRectToScene( mWorldFileMap->rect() );
2456 
2457  double alpha = mWorldFileMap->mapRotation() / 180 * M_PI;
2458 
2459  double xr = extent.width() / brect.width();
2460  double yr = extent.height() / brect.height();
2461 
2462  double XC = extent.center().x();
2463  double YC = extent.center().y();
2464 
2465  // get the extent for the page
2466  double xmin = extent.xMinimum() - mWorldFileMap->pos().x() * xr;
2467  double ymax = extent.yMaximum() + mWorldFileMap->pos().y() * yr;
2468  QgsRectangle paperExtent( xmin, ymax - paperHeight() * yr, xmin + paperWidth() * xr, ymax );
2469 
2470  double X0 = paperExtent.xMinimum();
2471  double Y0 = paperExtent.yMinimum();
2472 
2473  int widthPx = ( int )( printResolution() * paperWidth() / 25.4 );
2474  int heightPx = ( int )( printResolution() * paperHeight() / 25.4 );
2475 
2476  double Ww = paperExtent.width() / widthPx;
2477  double Hh = paperExtent.height() / heightPx;
2478 
2479  // scaling matrix
2480  double s[6];
2481  s[0] = Ww;
2482  s[1] = 0;
2483  s[2] = X0;
2484  s[3] = 0;
2485  s[4] = -Hh;
2486  s[5] = Y0 + paperExtent.height();
2487 
2488  // rotation matrix
2489  double r[6];
2490  r[0] = cos( alpha );
2491  r[1] = -sin( alpha );
2492  r[2] = XC * ( 1 - cos( alpha ) ) + YC * sin( alpha );
2493  r[3] = sin( alpha );
2494  r[4] = cos( alpha );
2495  r[5] = - XC * sin( alpha ) + YC * ( 1 - cos( alpha ) );
2496 
2497  // result = rotation x scaling = rotation(scaling(X))
2498  a = r[0] * s[0] + r[1] * s[3];
2499  b = r[0] * s[1] + r[1] * s[4];
2500  c = r[0] * s[2] + r[1] * s[5] + r[2];
2501  d = r[3] * s[0] + r[4] * s[3];
2502  e = r[3] * s[1] + r[4] * s[4];
2503  f = r[3] * s[2] + r[4] * s[5] + r[5];
2504 }
2505 
2507 {
2508  mAtlasMode = mode;
2509 
2510  if ( mode == QgsComposition::AtlasOff )
2511  {
2513  }
2514  else
2515  {
2516  bool atlasHasFeatures = mAtlasComposition.beginRender();
2517  if ( ! atlasHasFeatures )
2518  {
2520  return false;
2521  }
2522  }
2523 
2524  QList<QgsComposerMap*> maps;
2525  composerItems( maps );
2526  for ( QList<QgsComposerMap*>::iterator mit = maps.begin(); mit != maps.end(); ++mit )
2527  {
2528  QgsComposerMap* currentMap = ( *mit );
2529  if ( !currentMap->atlasDriven() )
2530  {
2531  continue;
2532  }
2533  currentMap->toggleAtlasPreview();
2534  }
2535 
2536  update();
2537  return true;
2538 }
2539 
2540 void QgsComposition::relativeResizeRect( QRectF& rectToResize, const QRectF& boundsBefore, const QRectF& boundsAfter )
2541 {
2542  //linearly scale rectToResize relative to the scaling from boundsBefore to boundsAfter
2543  double left = relativePosition( rectToResize.left(), boundsBefore.left(), boundsBefore.right(), boundsAfter.left(), boundsAfter.right() );
2544  double right = relativePosition( rectToResize.right(), boundsBefore.left(), boundsBefore.right(), boundsAfter.left(), boundsAfter.right() );
2545  double top = relativePosition( rectToResize.top(), boundsBefore.top(), boundsBefore.bottom(), boundsAfter.top(), boundsAfter.bottom() );
2546  double bottom = relativePosition( rectToResize.bottom(), boundsBefore.top(), boundsBefore.bottom(), boundsAfter.top(), boundsAfter.bottom() );
2547 
2548  rectToResize.setRect( left, top, right - left, bottom - top );
2549 }
2550 
2551 double QgsComposition::relativePosition( double position, double beforeMin, double beforeMax, double afterMin, double afterMax )
2552 {
2553  //calculate parameters for linear scale between before and after ranges
2554  double m = ( afterMax - afterMin ) / ( beforeMax - beforeMin );
2555  double c = afterMin - ( beforeMin * m );
2556 
2557  //return linearly scaled position
2558  return m * position + c;
2559 }