QGIS API Documentation  2.99.0-Master (0a63d1f)
qgscomposerpicture.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscomposerpicture.cpp
3  -------------------
4  begin : September 2005
5  copyright : (C) 2005 by Radim Blazek
6  email : [email protected]
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "qgscomposerpicture.h"
19 #include "qgscomposerutils.h"
20 #include "qgscomposermap.h"
21 #include "qgscomposition.h"
22 #include "qgsatlascomposition.h"
23 #include "qgsproject.h"
24 #include "qgsexpression.h"
25 #include "qgsvectorlayer.h"
26 #include "qgsmessagelog.h"
27 #include "qgsdatadefined.h"
29 #include "qgssymbollayerutils.h"
30 #include "qgssvgcache.h"
31 #include "qgslogger.h"
32 #include "qgsbearingutils.h"
33 #include "qgsmapsettings.h"
34 
35 #include <QDomDocument>
36 #include <QDomElement>
37 #include <QFileInfo>
38 #include <QImageReader>
39 #include <QPainter>
40 #include <QSvgRenderer>
41 #include <QNetworkRequest>
42 #include <QNetworkReply>
43 #include <QEventLoop>
44 #include <QCoreApplication>
45 
47  : QgsComposerItem( composition )
48  , mMode( Unknown )
49  , mPictureRotation( 0 )
50  , mRotationMap( nullptr )
51  , mNorthMode( GridNorth )
52  , mNorthOffset( 0.0 )
53  , mResizeMode( QgsComposerPicture::Zoom )
54  , mPictureAnchor( UpperLeft )
55  , mSvgFillColor( QColor( 255, 255, 255 ) )
56  , mSvgBorderColor( QColor( 0, 0, 0 ) )
57  , mSvgBorderWidth( 0.2 )
58  , mHasExpressionError( false )
59  , mLoadingSvg( false )
60 {
61  mPictureWidth = rect().width();
62  init();
63 }
64 
66  : QgsComposerItem( nullptr )
67  , mMode( Unknown )
68  , mPictureRotation( 0 )
69  , mRotationMap( nullptr )
70  , mNorthMode( GridNorth )
71  , mNorthOffset( 0.0 )
72  , mResizeMode( QgsComposerPicture::Zoom )
73  , mPictureAnchor( UpperLeft )
74  , mSvgFillColor( QColor( 255, 255, 255 ) )
75  , mSvgBorderColor( QColor( 0, 0, 0 ) )
76  , mSvgBorderWidth( 0.2 )
77  , mHasExpressionError( false )
78  , mLoadingSvg( false )
79 {
80  mPictureHeight = rect().height();
81  init();
82 }
83 
84 void QgsComposerPicture::init()
85 {
86  //default to no background
87  setBackgroundEnabled( false );
88 
89  //data defined strings
90  mDataDefinedNames.insert( QgsComposerObject::PictureSource, QStringLiteral( "dataDefinedSource" ) );
91 
92  //connect some signals
93 
94  //connect to atlas feature changing
95  //to update the picture source expression
96  connect( &mComposition->atlasComposition(), SIGNAL( featureChanged( QgsFeature* ) ), this, SLOT( refreshPicture() ) );
97 
98  //connect to composer print resolution changing
99  connect( mComposition, SIGNAL( printResolutionChanged() ), this, SLOT( recalculateSize() ) );
100 }
101 
102 void QgsComposerPicture::paint( QPainter* painter, const QStyleOptionGraphicsItem* itemStyle, QWidget* pWidget )
103 {
104  Q_UNUSED( itemStyle );
105  Q_UNUSED( pWidget );
106  if ( !painter )
107  {
108  return;
109  }
110  if ( !shouldDrawItem() )
111  {
112  return;
113  }
114 
115  drawBackground( painter );
116 
117  //int newDpi = ( painter->device()->logicalDpiX() + painter->device()->logicalDpiY() ) / 2;
118 
119  //picture resizing
120  if ( mMode != Unknown )
121  {
122  double boundRectWidthMM;
123  double boundRectHeightMM;
124  QRect imageRect;
125  if ( mResizeMode == QgsComposerPicture::Zoom || mResizeMode == QgsComposerPicture::ZoomResizeFrame )
126  {
127  boundRectWidthMM = mPictureWidth;
128  boundRectHeightMM = mPictureHeight;
129  imageRect = QRect( 0, 0, mImage.width(), mImage.height() );
130  }
131  else if ( mResizeMode == QgsComposerPicture::Stretch )
132  {
133  boundRectWidthMM = rect().width();
134  boundRectHeightMM = rect().height();
135  imageRect = QRect( 0, 0, mImage.width(), mImage.height() );
136  }
137  else if ( mResizeMode == QgsComposerPicture::Clip )
138  {
139  boundRectWidthMM = rect().width();
140  boundRectHeightMM = rect().height();
141  int imageRectWidthPixels = mImage.width();
142  int imageRectHeightPixels = mImage.height();
143  imageRect = clippedImageRect( boundRectWidthMM, boundRectHeightMM,
144  QSize( imageRectWidthPixels, imageRectHeightPixels ) );
145  }
146  else
147  {
148  boundRectWidthMM = rect().width();
149  boundRectHeightMM = rect().height();
150  imageRect = QRect( 0, 0, rect().width() * mComposition->printResolution() / 25.4,
151  rect().height() * mComposition->printResolution() / 25.4 );
152  }
153  painter->save();
154  //antialiasing on
155  painter->setRenderHint( QPainter::Antialiasing, true );
156 
157  //zoom mode - calculate anchor point and rotation
158  if ( mResizeMode == Zoom )
159  {
160  //TODO - allow placement modes with rotation set. for now, setting a rotation
161  //always places picture in center of frame
162  if ( !qgsDoubleNear( mPictureRotation, 0.0 ) )
163  {
164  painter->translate( rect().width() / 2.0, rect().height() / 2.0 );
165  painter->rotate( mPictureRotation );
166  painter->translate( -boundRectWidthMM / 2.0, -boundRectHeightMM / 2.0 );
167  }
168  else
169  {
170  //shift painter to edge/middle of frame depending on placement
171  double diffX = rect().width() - boundRectWidthMM;
172  double diffY = rect().height() - boundRectHeightMM;
173 
174  double dX = 0;
175  double dY = 0;
176  switch ( mPictureAnchor )
177  {
178  case UpperLeft:
179  case MiddleLeft:
180  case LowerLeft:
181  //nothing to do
182  break;
183  case UpperMiddle:
184  case Middle:
185  case LowerMiddle:
186  dX = diffX / 2.0;
187  break;
188  case UpperRight:
189  case MiddleRight:
190  case LowerRight:
191  dX = diffX;
192  break;
193  }
194  switch ( mPictureAnchor )
195  {
196  case UpperLeft:
197  case UpperMiddle:
198  case UpperRight:
199  //nothing to do
200  break;
201  case MiddleLeft:
202  case Middle:
203  case MiddleRight:
204  dY = diffY / 2.0;
205  break;
206  case LowerLeft:
207  case LowerMiddle:
208  case LowerRight:
209  dY = diffY;
210  break;
211  }
212  painter->translate( dX, dY );
213  }
214  }
215  else if ( mResizeMode == ZoomResizeFrame )
216  {
217  if ( !qgsDoubleNear( mPictureRotation, 0.0 ) )
218  {
219  painter->translate( rect().width() / 2.0, rect().height() / 2.0 );
220  painter->rotate( mPictureRotation );
221  painter->translate( -boundRectWidthMM / 2.0, -boundRectHeightMM / 2.0 );
222  }
223  }
224 
225  if ( mMode == SVG )
226  {
227  mSVG.render( painter, QRectF( 0, 0, boundRectWidthMM, boundRectHeightMM ) );
228  }
229  else if ( mMode == RASTER )
230  {
231  painter->drawImage( QRectF( 0, 0, boundRectWidthMM, boundRectHeightMM ), mImage, imageRect );
232  }
233 
234  painter->restore();
235  }
236 
237  //frame and selection boxes
238  drawFrame( painter );
239  if ( isSelected() )
240  {
241  drawSelectionBoxes( painter );
242  }
243 }
244 
245 QRect QgsComposerPicture::clippedImageRect( double &boundRectWidthMM, double &boundRectHeightMM, QSize imageRectPixels )
246 {
247  int boundRectWidthPixels = boundRectWidthMM * mComposition->printResolution() / 25.4;
248  int boundRectHeightPixels = boundRectHeightMM * mComposition->printResolution() / 25.4;
249 
250  //update boundRectWidth/Height so that they exactly match pixel bounds
251  boundRectWidthMM = boundRectWidthPixels * 25.4 / mComposition->printResolution();
252  boundRectHeightMM = boundRectHeightPixels * 25.4 / mComposition->printResolution();
253 
254  //calculate part of image which fits in bounds
255  int leftClip = 0;
256  int topClip = 0;
257 
258  //calculate left crop
259  switch ( mPictureAnchor )
260  {
261  case UpperLeft:
262  case MiddleLeft:
263  case LowerLeft:
264  leftClip = 0;
265  break;
266  case UpperMiddle:
267  case Middle:
268  case LowerMiddle:
269  leftClip = ( imageRectPixels.width() - boundRectWidthPixels ) / 2;
270  break;
271  case UpperRight:
272  case MiddleRight:
273  case LowerRight:
274  leftClip = imageRectPixels.width() - boundRectWidthPixels;
275  break;
276  }
277 
278  //calculate top crop
279  switch ( mPictureAnchor )
280  {
281  case UpperLeft:
282  case UpperMiddle:
283  case UpperRight:
284  topClip = 0;
285  break;
286  case MiddleLeft:
287  case Middle:
288  case MiddleRight:
289  topClip = ( imageRectPixels.height() - boundRectHeightPixels ) / 2;
290  break;
291  case LowerLeft:
292  case LowerMiddle:
293  case LowerRight:
294  topClip = imageRectPixels.height() - boundRectHeightPixels;
295  break;
296  }
297 
298  return QRect( leftClip, topClip, boundRectWidthPixels, boundRectHeightPixels );
299 }
300 
302 {
304  const QgsExpressionContext* evalContext = context ? context : &scopedContext;
305 
306  QString source = mSourcePath;
307 
308  //data defined source set?
309  mHasExpressionError = false;
310  QVariant exprVal;
313  {
314  if ( dataDefinedEvaluate( QgsComposerObject::PictureSource, exprVal, *evalContext ) )
315  {
316  source = exprVal.toString().trimmed();
317  QgsDebugMsg( QString( "exprVal PictureSource:%1" ).arg( source ) );
318  }
319  else
320  {
321  mHasExpressionError = true;
322  source = QString();
323  QgsMessageLog::logMessage( tr( "Picture expression eval error" ) );
324  }
325  }
326 
327  loadPicture( source );
328 }
329 
330 void QgsComposerPicture::loadRemotePicture( const QString &url )
331 {
332  //remote location
333 
334  QgsNetworkContentFetcher fetcher;
335  //pause until HTML fetch
336  mLoaded = false;
337  fetcher.fetchContent( QUrl( url ) );
338  connect( &fetcher, SIGNAL( finished() ), this, SLOT( remotePictureLoaded() ) );
339 
340  while ( !mLoaded )
341  {
342  qApp->processEvents();
343  }
344 
345  QNetworkReply* reply = fetcher.reply();
346  if ( reply )
347  {
348  QImageReader imageReader( reply );
349  mImage = imageReader.read();
350  mMode = RASTER;
351  reply->deleteLater();
352  }
353  else
354  {
355  mMode = Unknown;
356  }
357 }
358 
359 void QgsComposerPicture::loadLocalPicture( const QString &path )
360 {
361  QFile pic;
362  pic.setFileName( path );
363 
364  if ( !pic.exists() )
365  {
366  mMode = Unknown;
367  }
368  else
369  {
370  QFileInfo sourceFileInfo( pic );
371  QString sourceFileSuffix = sourceFileInfo.suffix();
372  if ( sourceFileSuffix.compare( QLatin1String( "svg" ), Qt::CaseInsensitive ) == 0 )
373  {
374  //try to open svg
375  const QByteArray &svgContent = QgsApplication::svgCache()->svgContent( pic.fileName(), rect().width(), mSvgFillColor, mSvgBorderColor, mSvgBorderWidth,
376  1.0 );
377  mSVG.load( svgContent );
378  if ( mSVG.isValid() )
379  {
380  mMode = SVG;
381  QRect viewBox = mSVG.viewBox(); //take width/height ratio from view box instead of default size
382  mDefaultSvgSize.setWidth( viewBox.width() );
383  mDefaultSvgSize.setHeight( viewBox.height() );
384  }
385  else
386  {
387  mMode = Unknown;
388  }
389  }
390  else
391  {
392  //try to open raster with QImageReader
393  QImageReader imageReader( pic.fileName() );
394  if ( imageReader.read( &mImage ) )
395  {
396  mMode = RASTER;
397  }
398  else
399  {
400  mMode = Unknown;
401  }
402  }
403  }
404 
405 }
406 
407 void QgsComposerPicture::remotePictureLoaded()
408 {
409  mLoaded = true;
410 }
411 
412 void QgsComposerPicture::updateMapRotation()
413 {
414  if ( !mRotationMap )
415  return;
416 
417  // take map rotation
418  double rotation = mRotationMap->mapRotation();
419 
420  // handle true north
421  switch ( mNorthMode )
422  {
423  case GridNorth:
424  break; // nothing to do
425 
426  case TrueNorth:
427  {
428  QgsPoint center = mRotationMap->currentMapExtent()->center();
430 
431  try
432  {
433  double bearing = QgsBearingUtils::bearingTrueNorth( crs, center );
434  rotation += bearing;
435  }
436  catch ( QgsException& e )
437  {
438  Q_UNUSED( e );
439  QgsDebugMsg( QString( "Caught exception %1" ).arg( e.what() ) );
440  }
441  break;
442  }
443  }
444 
445  rotation += mNorthOffset;
446  setPictureRotation( rotation );
447 }
448 
449 void QgsComposerPicture::loadPicture( const QString &path )
450 {
451  if ( path.startsWith( QLatin1String( "http" ) ) )
452  {
453  //remote location
454  loadRemotePicture( path );
455  }
456  else
457  {
458  //local location
459  loadLocalPicture( path );
460  }
461  if ( mMode != Unknown ) //make sure we start with a new QImage
462  {
463  recalculateSize();
464  }
465  else if ( mHasExpressionError || !( path.isEmpty() ) )
466  {
467  //trying to load an invalid file or bad expression, show cross picture
468  mMode = SVG;
469  QString badFile( QStringLiteral( ":/images/composer/missing_image.svg" ) );
470  mSVG.load( badFile );
471  if ( mSVG.isValid() )
472  {
473  mMode = SVG;
474  QRect viewBox = mSVG.viewBox(); //take width/height ratio from view box instead of default size
475  mDefaultSvgSize.setWidth( viewBox.width() );
476  mDefaultSvgSize.setHeight( viewBox.height() );
477  recalculateSize();
478  }
479  }
480 
481  emit itemChanged();
482 }
483 
484 QRectF QgsComposerPicture::boundedImageRect( double deviceWidth, double deviceHeight )
485 {
486  double imageToDeviceRatio;
487  if ( mImage.width() / deviceWidth > mImage.height() / deviceHeight )
488  {
489  imageToDeviceRatio = deviceWidth / mImage.width();
490  double height = imageToDeviceRatio * mImage.height();
491  return QRectF( 0, 0, deviceWidth, height );
492  }
493  else
494  {
495  imageToDeviceRatio = deviceHeight / mImage.height();
496  double width = imageToDeviceRatio * mImage.width();
497  return QRectF( 0, 0, width, deviceHeight );
498  }
499 }
500 
501 QRectF QgsComposerPicture::boundedSVGRect( double deviceWidth, double deviceHeight )
502 {
503  double imageToSvgRatio;
504  if ( deviceWidth / mDefaultSvgSize.width() > deviceHeight / mDefaultSvgSize.height() )
505  {
506  imageToSvgRatio = deviceHeight / mDefaultSvgSize.height();
507  double width = mDefaultSvgSize.width() * imageToSvgRatio;
508  return QRectF( 0, 0, width, deviceHeight );
509  }
510  else
511  {
512  imageToSvgRatio = deviceWidth / mDefaultSvgSize.width();
513  double height = mDefaultSvgSize.height() * imageToSvgRatio;
514  return QRectF( 0, 0, deviceWidth, height );
515  }
516 }
517 
518 QSizeF QgsComposerPicture::pictureSize()
519 {
520  if ( mMode == SVG )
521  {
522  return mDefaultSvgSize;
523  }
524  else if ( mMode == RASTER )
525  {
526  return QSizeF( mImage.width(), mImage.height() );
527  }
528  else
529  {
530  return QSizeF( 0, 0 );
531  }
532 }
533 
534 #if 0
535 QRectF QgsComposerPicture::boundedSVGRect( double deviceWidth, double deviceHeight )
536 {
537  double imageToSvgRatio;
538  if ( deviceWidth / mDefaultSvgSize.width() < deviceHeight / mDefaultSvgSize.height() )
539  {
540  imageToSvgRatio = deviceWidth / mDefaultSvgSize.width();
541  double height = mDefaultSvgSize.height() * imageToSvgRatio;
542  return QRectF( 0, 0, deviceWidth, height );
543  }
544  else
545  {
546  imageToSvgRatio = deviceHeight / mDefaultSvgSize.height();
547  double width = mDefaultSvgSize.width() * imageToSvgRatio;
548  return QRectF( 0, 0, width, deviceHeight );
549  }
550 }
551 #endif //0
552 
553 void QgsComposerPicture::setSceneRect( const QRectF& rectangle )
554 {
555  QSizeF currentPictureSize = pictureSize();
556 
557  if ( mResizeMode == QgsComposerPicture::Clip )
558  {
559  QgsComposerItem::setSceneRect( rectangle );
560  mPictureWidth = rectangle.width();
561  mPictureHeight = rectangle.height();
562  }
563  else
564  {
565  QRectF newRect = rectangle;
566 
567  if ( mResizeMode == ZoomResizeFrame && !rect().isEmpty() && !( currentPictureSize.isEmpty() ) )
568  {
569  QSizeF targetImageSize;
570  if ( qgsDoubleNear( mPictureRotation, 0.0 ) )
571  {
572  targetImageSize = currentPictureSize;
573  }
574  else
575  {
576  //calculate aspect ratio of bounds of rotated image
577  QTransform tr;
578  tr.rotate( mPictureRotation );
579  QRectF rotatedBounds = tr.mapRect( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ) );
580  targetImageSize = QSizeF( rotatedBounds.width(), rotatedBounds.height() );
581  }
582 
583  //if height has changed more than width, then fix width and set height correspondingly
584  //else, do the opposite
585  if ( qAbs( rect().width() - rectangle.width() ) <
586  qAbs( rect().height() - rectangle.height() ) )
587  {
588  newRect.setHeight( targetImageSize.height() * newRect.width() / targetImageSize.width() );
589  }
590  else
591  {
592  newRect.setWidth( targetImageSize.width() * newRect.height() / targetImageSize.height() );
593  }
594  }
595  else if ( mResizeMode == FrameToImageSize )
596  {
597  if ( !( currentPictureSize.isEmpty() ) )
598  {
599  newRect.setWidth( currentPictureSize.width() * 25.4 / mComposition->printResolution() );
600  newRect.setHeight( currentPictureSize.height() * 25.4 / mComposition->printResolution() );
601  }
602  }
603 
604  //find largest scaling of picture with this rotation which fits in item
605  if ( mResizeMode == Zoom || mResizeMode == ZoomResizeFrame )
606  {
607  QRectF rotatedImageRect = QgsComposerUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ), newRect, mPictureRotation );
608  mPictureWidth = rotatedImageRect.width();
609  mPictureHeight = rotatedImageRect.height();
610  }
611  else
612  {
613  mPictureWidth = newRect.width();
614  mPictureHeight = newRect.height();
615  }
616 
618  emit itemChanged();
619  }
620 
621  if ( mMode == SVG && !mLoadingSvg )
622  {
623  mLoadingSvg = true;
624  refreshPicture();
625  mLoadingSvg = false;
626  }
627 }
628 
630 {
631  double oldRotation = mPictureRotation;
632  mPictureRotation = r;
633 
634  if ( mResizeMode == Zoom )
635  {
636  //find largest scaling of picture with this rotation which fits in item
637  QSizeF currentPictureSize = pictureSize();
638  QRectF rotatedImageRect = QgsComposerUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ), rect(), mPictureRotation );
639  mPictureWidth = rotatedImageRect.width();
640  mPictureHeight = rotatedImageRect.height();
641  update();
642  }
643  else if ( mResizeMode == ZoomResizeFrame )
644  {
645  QSizeF currentPictureSize = pictureSize();
646  QRectF oldRect = QRectF( pos().x(), pos().y(), rect().width(), rect().height() );
647 
648  //calculate actual size of image inside frame
649  QRectF rotatedImageRect = QgsComposerUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ), rect(), oldRotation );
650 
651  //rotate image rect by new rotation and get bounding box
652  QTransform tr;
653  tr.rotate( mPictureRotation );
654  QRectF newRect = tr.mapRect( QRectF( 0, 0, rotatedImageRect.width(), rotatedImageRect.height() ) );
655 
656  //keep the center in the same location
657  newRect.moveCenter( oldRect.center() );
659  emit itemChanged();
660  }
661 
662  emit pictureRotationChanged( mPictureRotation );
663 }
664 
665 void QgsComposerPicture::setRotationMap( int composerMapId )
666 {
667  if ( !mComposition )
668  {
669  return;
670  }
671 
672  if ( composerMapId == -1 ) //disable rotation from map
673  {
674  disconnect( mRotationMap, SIGNAL( mapRotationChanged( double ) ), this, SLOT( updateMapRotation() ) );
675  disconnect( mRotationMap, SIGNAL( extentChanged() ), this, SLOT( updateMapRotation() ) );
676  mRotationMap = nullptr;
677  }
678 
679  const QgsComposerMap* map = mComposition->getComposerMapById( composerMapId );
680  if ( !map )
681  {
682  return;
683  }
684  if ( mRotationMap )
685  {
686  disconnect( mRotationMap, SIGNAL( mapRotationChanged( double ) ), this, SLOT( updateMapRotation() ) );
687  disconnect( mRotationMap, SIGNAL( extentChanged() ), this, SLOT( updateMapRotation() ) );
688  }
689  mPictureRotation = map->mapRotation();
690  connect( map, SIGNAL( mapRotationChanged( double ) ), this, SLOT( updateMapRotation() ) );
691  connect( map, SIGNAL( extentChanged() ), this, SLOT( updateMapRotation() ) );
692  mRotationMap = map;
693  updateMapRotation();
694  emit pictureRotationChanged( mPictureRotation );
695 }
696 
698 {
699  mResizeMode = mode;
701  || ( mode == QgsComposerPicture::Zoom && !qgsDoubleNear( mPictureRotation, 0.0 ) ) )
702  {
703  //call set scene rect to force item to resize to fit picture
704  recalculateSize();
705  }
706  update();
707 }
708 
710 {
711  //call set scene rect with current position/size, as this will trigger the
712  //picture item to recalculate its frame and image size
713  setSceneRect( QRectF( pos().x(), pos().y(), rect().width(), rect().height() ) );
714 }
715 
717 {
719  const QgsExpressionContext* evalContext = context ? context : &scopedContext;
720 
722  {
723  refreshPicture( evalContext );
724  }
725 
726  QgsComposerItem::refreshDataDefinedProperty( property, evalContext );
727 }
728 
729 void QgsComposerPicture::setPicturePath( const QString &path )
730 {
731  mSourcePath = path;
732  refreshPicture();
733 }
734 
736 {
737  return mSourcePath;
738 }
739 
740 bool QgsComposerPicture::writeXml( QDomElement& elem, QDomDocument & doc ) const
741 {
742  if ( elem.isNull() )
743  {
744  return false;
745  }
746  QDomElement composerPictureElem = doc.createElement( QStringLiteral( "ComposerPicture" ) );
747  composerPictureElem.setAttribute( QStringLiteral( "file" ), mComposition->project()->writePath( mSourcePath ) );
748  composerPictureElem.setAttribute( QStringLiteral( "pictureWidth" ), QString::number( mPictureWidth ) );
749  composerPictureElem.setAttribute( QStringLiteral( "pictureHeight" ), QString::number( mPictureHeight ) );
750  composerPictureElem.setAttribute( QStringLiteral( "resizeMode" ), QString::number( static_cast< int >( mResizeMode ) ) );
751  composerPictureElem.setAttribute( QStringLiteral( "anchorPoint" ), QString::number( static_cast< int >( mPictureAnchor ) ) );
752  composerPictureElem.setAttribute( QStringLiteral( "svgFillColor" ), QgsSymbolLayerUtils::encodeColor( mSvgFillColor ) );
753  composerPictureElem.setAttribute( QStringLiteral( "svgBorderColor" ), QgsSymbolLayerUtils::encodeColor( mSvgBorderColor ) );
754  composerPictureElem.setAttribute( QStringLiteral( "svgBorderWidth" ), QString::number( mSvgBorderWidth ) );
755 
756  //rotation
757  composerPictureElem.setAttribute( QStringLiteral( "pictureRotation" ), QString::number( mPictureRotation ) );
758  if ( !mRotationMap )
759  {
760  composerPictureElem.setAttribute( QStringLiteral( "mapId" ), -1 );
761  }
762  else
763  {
764  composerPictureElem.setAttribute( QStringLiteral( "mapId" ), mRotationMap->id() );
765  }
766  composerPictureElem.setAttribute( QStringLiteral( "northMode" ), mNorthMode );
767  composerPictureElem.setAttribute( QStringLiteral( "northOffset" ), mNorthOffset );
768 
769  _writeXml( composerPictureElem, doc );
770  elem.appendChild( composerPictureElem );
771  return true;
772 }
773 
774 bool QgsComposerPicture::readXml( const QDomElement& itemElem, const QDomDocument& doc )
775 {
776  if ( itemElem.isNull() )
777  {
778  return false;
779  }
780 
781  mPictureWidth = itemElem.attribute( QStringLiteral( "pictureWidth" ), QStringLiteral( "10" ) ).toDouble();
782  mPictureHeight = itemElem.attribute( QStringLiteral( "pictureHeight" ), QStringLiteral( "10" ) ).toDouble();
783  mResizeMode = QgsComposerPicture::ResizeMode( itemElem.attribute( QStringLiteral( "resizeMode" ), QStringLiteral( "0" ) ).toInt() );
784  //when loading from xml, default to anchor point of middle to match pre 2.4 behavior
785  mPictureAnchor = static_cast< QgsComposerItem::ItemPositionMode >( itemElem.attribute( QStringLiteral( "anchorPoint" ), QString::number( QgsComposerItem::Middle ) ).toInt() );
786 
787  mSvgFillColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "svgFillColor" ), QgsSymbolLayerUtils::encodeColor( QColor( 255, 255, 255 ) ) ) );
788  mSvgBorderColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "svgBorderColor" ), QgsSymbolLayerUtils::encodeColor( QColor( 0, 0, 0 ) ) ) );
789  mSvgBorderWidth = itemElem.attribute( QStringLiteral( "svgBorderWidth" ), QStringLiteral( "0.2" ) ).toDouble();
790 
791  QDomNodeList composerItemList = itemElem.elementsByTagName( QStringLiteral( "ComposerItem" ) );
792  if ( !composerItemList.isEmpty() )
793  {
794  QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
795 
796  if ( !qgsDoubleNear( composerItemElem.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble(), 0.0 ) )
797  {
798  //in versions prior to 2.1 picture rotation was stored in the rotation attribute
799  mPictureRotation = composerItemElem.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble();
800  }
801 
802  _readXml( composerItemElem, doc );
803  }
804 
805  mDefaultSvgSize = QSize( 0, 0 );
806 
807  if ( itemElem.hasAttribute( QStringLiteral( "sourceExpression" ) ) )
808  {
809  //update pre 2.5 picture expression to use data defined expression
810  QString sourceExpression = itemElem.attribute( QStringLiteral( "sourceExpression" ), QLatin1String( "" ) );
811  QString useExpression = itemElem.attribute( QStringLiteral( "useExpression" ) );
812  bool expressionActive;
813  if ( useExpression.compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0 )
814  {
815  expressionActive = true;
816  }
817  else
818  {
819  expressionActive = false;
820  }
821 
822  setDataDefinedProperty( QgsComposerObject::PictureSource, expressionActive, true, sourceExpression, QString() );
823  }
824 
825  mSourcePath = mComposition->project()->readPath( itemElem.attribute( QStringLiteral( "file" ) ) );
826 
827  //picture rotation
828  if ( !qgsDoubleNear( itemElem.attribute( QStringLiteral( "pictureRotation" ), QStringLiteral( "0" ) ).toDouble(), 0.0 ) )
829  {
830  mPictureRotation = itemElem.attribute( QStringLiteral( "pictureRotation" ), QStringLiteral( "0" ) ).toDouble();
831  }
832 
833  //rotation map
834  mNorthMode = static_cast< NorthMode >( itemElem.attribute( QStringLiteral( "northMode" ), QStringLiteral( "0" ) ).toInt() );
835  mNorthOffset = itemElem.attribute( QStringLiteral( "northOffset" ), QStringLiteral( "0" ) ).toDouble();
836 
837  int rotationMapId = itemElem.attribute( QStringLiteral( "mapId" ), QStringLiteral( "-1" ) ).toInt();
838  if ( rotationMapId == -1 )
839  {
840  mRotationMap = nullptr;
841  }
842  else if ( mComposition )
843  {
844 
845  if ( mRotationMap )
846  {
847  disconnect( mRotationMap, SIGNAL( mapRotationChanged( double ) ), this, SLOT( updateMapRotation() ) );
848  disconnect( mRotationMap, SIGNAL( extentChanged() ), this, SLOT( updateMapRotation() ) );
849  }
850  mRotationMap = mComposition->getComposerMapById( rotationMapId );
851  connect( mRotationMap, SIGNAL( mapRotationChanged( double ) ), this, SLOT( updateMapRotation() ) );
852  connect( mRotationMap, SIGNAL( extentChanged() ), this, SLOT( updateMapRotation() ) );
853  }
854 
855  refreshPicture();
856 
857  emit itemChanged();
858  return true;
859 }
860 
862 {
863  if ( !mRotationMap )
864  {
865  return -1;
866  }
867  else
868  {
869  return mRotationMap->id();
870  }
871 }
872 
874 {
875  mNorthMode = mode;
876  updateMapRotation();
877 }
878 
880 {
881  mNorthOffset = offset;
882  updateMapRotation();
883 }
884 
886 {
887  mPictureAnchor = anchor;
888  update();
889 }
890 
891 void QgsComposerPicture::setSvgFillColor( const QColor& color )
892 {
893  mSvgFillColor = color;
894  refreshPicture();
895 }
896 
897 void QgsComposerPicture::setSvgBorderColor( const QColor& color )
898 {
899  mSvgBorderColor = color;
900  refreshPicture();
901 }
902 
904 {
905  mSvgBorderWidth = width;
906  refreshPicture();
907 }
Enlarges image to fit frame while maintaining aspect ratio of picture.
static QgsSvgCache * svgCache()
Returns the application&#39;s SVG cache, used for caching SVG images and handling parameter replacement w...
QgsPoint center() const
Center point of the rectangle.
Definition: qgsrectangle.h:222
const QgsComposerMap * getComposerMapById(const int id) const
Returns the composer map with specified id.
bool shouldDrawItem() const
Returns whether the item should be drawn in the current context.
Stretches image to fit frame, ignores aspect ratio.
#define QgsDebugMsg(str)
Definition: qgslogger.h:36
void itemChanged()
Emitted when the item changes.
QgsComposerPicture(QgsComposition *composition)
int printResolution() const
void setSvgBorderWidth(double width)
Sets the border width used for parametrized SVG files.
QMap< QgsComposerObject::DataDefinedProperty, QString > mDataDefinedNames
Map of data defined properties for the item to string name to use when exporting item to xml...
A item that forms part of a map composition.
QString writePath(const QString &filename, const QString &relativeBasePath=QString::null) const
Prepare a filename to save it to the project file.
int id() const
Get identification number.
Sets size of frame to match original size of image without scaling.
virtual void drawFrame(QPainter *p)
Draw black frame around item.
QNetworkReply * reply()
Returns a reference to the network reply.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:136
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Compare two doubles (but allow some difference)
Definition: qgis.h:193
DataDefinedProperty
Data defined properties for different item types.
QByteArray svgContent(const QString &file, double size, const QColor &fill, const QColor &outline, double outlineWidth, double widthScaleFactor)
Get SVG content.
QgsCoordinateReferenceSystem destinationCrs() const
returns CRS of destination coordinate reference system
A composer class that displays svg files or raster format (jpg, png, ...)
QString what() const
Definition: qgsexception.h:38
void recalculateSize()
Forces a recalculation of the picture&#39;s frame size.
void setSvgFillColor(const QColor &color)
Sets the fill color used for parametrized SVG files.
static QString encodeColor(const QColor &color)
int rotationMap() const
Returns the id of the rotation map.
void setPictureAnchor(QgsComposerItem::ItemPositionMode anchor)
Sets the picture&#39;s anchor point, which controls how it is placed within the picture item&#39;s frame...
bool _writeXml(QDomElement &itemElem, QDomDocument &doc) const
Writes parameter that are not subclass specific in document. Usually called from writeXml methods of ...
void paint(QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget) override
Reimplementation of QCanvasItem::paint.
ResizeMode
Controls how pictures are scaled within the item&#39;s frame.
NorthMode
Method for syncing rotation to a map&#39;s North direction.
HTTP network content fetcher.
virtual void drawSelectionBoxes(QPainter *p)
Draws additional graphics on selected items.
virtual void setResizeMode(ResizeMode mode)
Sets the resize mode used for drawing the picture within the item bounds.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void setNorthMode(NorthMode mode)
Sets the mode used to align the picture to a map&#39;s North.
static QRectF largestRotatedRectWithinBounds(const QRectF &originalRect, const QRectF &boundsRect, const double rotation)
Calculates the largest scaled version of originalRect which fits within boundsRect, when it is rotated by a specified amount.
static void logMessage(const QString &message, const QString &tag=QString::null, MessageLevel level=WARNING)
add a message to the instance (and create it if necessary)
bool dataDefinedEvaluate(const QgsComposerObject::DataDefinedProperty property, QVariant &expressionValue, const QgsExpressionContext &context=QgsExpressionContext()) const
Evaluate a data defined property and return the calculated value.
virtual void refreshDataDefinedProperty(const QgsComposerObject::DataDefinedProperty property=QgsComposerObject::AllProperties, const QgsExpressionContext *context=nullptr) override
Refreshes a data defined property for the item by reevaluating the property&#39;s value and redrawing the...
void setNorthOffset(double offset)
Sets the offset added to the picture&#39;s rotation from a map&#39;s North.
bool _readXml(const QDomElement &itemElem, const QDomDocument &doc)
Reads parameter that are not subclass specific in document. Usually called from readXml methods of su...
void setBackgroundEnabled(const bool drawBackground)
Set whether this item has a Background drawn around it or not.
virtual void refreshDataDefinedProperty(const QgsComposerObject::DataDefinedProperty property=QgsComposerObject::AllProperties, const QgsExpressionContext *context=nullptr) override
A class to represent a point.
Definition: qgspoint.h:143
Graphics scene for map printing.
const QgsMapSettings & mapSettings() const
Return setting of QGIS map canvas.
Object representing map window.
Enlarges image to fit frame, then resizes frame to fit resultant image.
Draws image at original size and clips any portion which falls outside frame.
void pictureRotationChanged(double newRotation)
Is emitted on picture rotation change.
virtual QgsExpressionContext createExpressionContext() const override
Creates an expression context relating to the item&#39;s current state.
QgsDataDefined * dataDefinedProperty(const DataDefinedProperty property) const
Returns a reference to the data defined settings for one of the item&#39;s data defined properties...
QgsComposition * mComposition
void setSvgBorderColor(const QColor &color)
Sets the border color used for parametrized SVG files.
QgsProject * project() const
The project associated with the composition.
virtual void drawBackground(QPainter *p)
Draw background.
Mode mode() const
Returns the current picture mode (image format).
This class represents a coordinate reference system (CRS).
virtual void setSceneRect(const QRectF &rectangle)
Sets this items bound in scene coordinates such that 1 item size units corresponds to 1 scene size un...
void setDataDefinedProperty(const DataDefinedProperty property, const bool active, const bool useExpression, const QString &expression, const QString &field)
Sets parameters for a data defined property for the item.
QString readPath(QString filename) const
Turn filename read from the project file to an absolute path.
void setSceneRect(const QRectF &rectangle) override
Sets this items bound in scene coordinates such that 1 item size units corresponds to 1 scene size un...
QgsAtlasComposition & atlasComposition()
void setRotationMap(int composerMapId)
Sets the map object for rotation (by id).
void setPicturePath(const QString &path)
Sets the source path of the image (may be svg or a raster format).
QgsComposerItem(QgsComposition *composition, bool manageZValue=true)
Constructor.
virtual void setPictureRotation(double r)
Sets the picture rotation within the item bounds.
double mapRotation(QgsComposerObject::PropertyValueType valueType=QgsComposerObject::EvaluatedValue) const
Returns the rotation used for drawing the map within the composer item.
void fetchContent(const QUrl &url)
Fetches content from a remote URL and handles redirects.
static double bearingTrueNorth(const QgsCoordinateReferenceSystem &crs, const QgsPoint &point)
Returns the direction to true north from a specified point and for a specified coordinate reference s...
bool writeXml(QDomElement &elem, QDomDocument &doc) const override
Stores state in Dom element.
bool readXml(const QDomElement &itemElem, const QDomDocument &doc) override
Sets state from Dom document.
Defines a qgis exception class.
Definition: qgsexception.h:27
const QgsRectangle * currentMapExtent() const
Returns a pointer to the current map extent, which is either the original user specified extent or th...
void refreshPicture(const QgsExpressionContext *context=nullptr)
Recalculates the source image (if using an expression for picture&#39;s source) and reloads and redraws t...
static QColor decodeColor(const QString &str)
QString picturePath() const
Returns the path of the source image.
All properties for item.