QGIS API Documentation  2.99.0-Master (b681b7b)
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 "qgsproperty.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  , mHasExpressionError( false )
56  , mLoadingSvg( false )
57 {
58  mPictureWidth = rect().width();
59  init();
60 }
61 
63  : QgsComposerItem( nullptr )
64  , mMode( Unknown )
65  , mPictureRotation( 0 )
66  , mRotationMap( nullptr )
67  , mNorthMode( GridNorth )
68  , mNorthOffset( 0.0 )
69  , mResizeMode( QgsComposerPicture::Zoom )
70  , mPictureAnchor( UpperLeft )
71  , mHasExpressionError( false )
72  , mLoadingSvg( false )
73 {
74  mPictureHeight = rect().height();
75  init();
76 }
77 
78 void QgsComposerPicture::init()
79 {
80  //default to no background
81  setBackgroundEnabled( false );
82 
83  //connect some signals
84 
85  //connect to atlas feature changing
86  //to update the picture source expression
87  connect( &mComposition->atlasComposition(), SIGNAL( featureChanged( QgsFeature* ) ), this, SLOT( refreshPicture() ) );
88 
89  //connect to composer print resolution changing
90  connect( mComposition, SIGNAL( printResolutionChanged() ), this, SLOT( recalculateSize() ) );
91 }
92 
93 void QgsComposerPicture::paint( QPainter* painter, const QStyleOptionGraphicsItem* itemStyle, QWidget* pWidget )
94 {
95  Q_UNUSED( itemStyle );
96  Q_UNUSED( pWidget );
97  if ( !painter )
98  {
99  return;
100  }
101  if ( !shouldDrawItem() )
102  {
103  return;
104  }
105 
106  drawBackground( painter );
107 
108  //int newDpi = ( painter->device()->logicalDpiX() + painter->device()->logicalDpiY() ) / 2;
109 
110  //picture resizing
111  if ( mMode != Unknown )
112  {
113  double boundRectWidthMM;
114  double boundRectHeightMM;
115  QRect imageRect;
116  if ( mResizeMode == QgsComposerPicture::Zoom || mResizeMode == QgsComposerPicture::ZoomResizeFrame )
117  {
118  boundRectWidthMM = mPictureWidth;
119  boundRectHeightMM = mPictureHeight;
120  imageRect = QRect( 0, 0, mImage.width(), mImage.height() );
121  }
122  else if ( mResizeMode == QgsComposerPicture::Stretch )
123  {
124  boundRectWidthMM = rect().width();
125  boundRectHeightMM = rect().height();
126  imageRect = QRect( 0, 0, mImage.width(), mImage.height() );
127  }
128  else if ( mResizeMode == QgsComposerPicture::Clip )
129  {
130  boundRectWidthMM = rect().width();
131  boundRectHeightMM = rect().height();
132  int imageRectWidthPixels = mImage.width();
133  int imageRectHeightPixels = mImage.height();
134  imageRect = clippedImageRect( boundRectWidthMM, boundRectHeightMM,
135  QSize( imageRectWidthPixels, imageRectHeightPixels ) );
136  }
137  else
138  {
139  boundRectWidthMM = rect().width();
140  boundRectHeightMM = rect().height();
141  imageRect = QRect( 0, 0, rect().width() * mComposition->printResolution() / 25.4,
142  rect().height() * mComposition->printResolution() / 25.4 );
143  }
144  painter->save();
145  //antialiasing on
146  painter->setRenderHint( QPainter::Antialiasing, true );
147 
148  //zoom mode - calculate anchor point and rotation
149  if ( mResizeMode == Zoom )
150  {
151  //TODO - allow placement modes with rotation set. for now, setting a rotation
152  //always places picture in center of frame
153  if ( !qgsDoubleNear( mPictureRotation, 0.0 ) )
154  {
155  painter->translate( rect().width() / 2.0, rect().height() / 2.0 );
156  painter->rotate( mPictureRotation );
157  painter->translate( -boundRectWidthMM / 2.0, -boundRectHeightMM / 2.0 );
158  }
159  else
160  {
161  //shift painter to edge/middle of frame depending on placement
162  double diffX = rect().width() - boundRectWidthMM;
163  double diffY = rect().height() - boundRectHeightMM;
164 
165  double dX = 0;
166  double dY = 0;
167  switch ( mPictureAnchor )
168  {
169  case UpperLeft:
170  case MiddleLeft:
171  case LowerLeft:
172  //nothing to do
173  break;
174  case UpperMiddle:
175  case Middle:
176  case LowerMiddle:
177  dX = diffX / 2.0;
178  break;
179  case UpperRight:
180  case MiddleRight:
181  case LowerRight:
182  dX = diffX;
183  break;
184  }
185  switch ( mPictureAnchor )
186  {
187  case UpperLeft:
188  case UpperMiddle:
189  case UpperRight:
190  //nothing to do
191  break;
192  case MiddleLeft:
193  case Middle:
194  case MiddleRight:
195  dY = diffY / 2.0;
196  break;
197  case LowerLeft:
198  case LowerMiddle:
199  case LowerRight:
200  dY = diffY;
201  break;
202  }
203  painter->translate( dX, dY );
204  }
205  }
206  else if ( mResizeMode == ZoomResizeFrame )
207  {
208  if ( !qgsDoubleNear( mPictureRotation, 0.0 ) )
209  {
210  painter->translate( rect().width() / 2.0, rect().height() / 2.0 );
211  painter->rotate( mPictureRotation );
212  painter->translate( -boundRectWidthMM / 2.0, -boundRectHeightMM / 2.0 );
213  }
214  }
215 
216  if ( mMode == SVG )
217  {
218  mSVG.render( painter, QRectF( 0, 0, boundRectWidthMM, boundRectHeightMM ) );
219  }
220  else if ( mMode == RASTER )
221  {
222  painter->drawImage( QRectF( 0, 0, boundRectWidthMM, boundRectHeightMM ), mImage, imageRect );
223  }
224 
225  painter->restore();
226  }
227 
228  //frame and selection boxes
229  drawFrame( painter );
230  if ( isSelected() )
231  {
232  drawSelectionBoxes( painter );
233  }
234 }
235 
236 QRect QgsComposerPicture::clippedImageRect( double &boundRectWidthMM, double &boundRectHeightMM, QSize imageRectPixels )
237 {
238  int boundRectWidthPixels = boundRectWidthMM * mComposition->printResolution() / 25.4;
239  int boundRectHeightPixels = boundRectHeightMM * mComposition->printResolution() / 25.4;
240 
241  //update boundRectWidth/Height so that they exactly match pixel bounds
242  boundRectWidthMM = boundRectWidthPixels * 25.4 / mComposition->printResolution();
243  boundRectHeightMM = boundRectHeightPixels * 25.4 / mComposition->printResolution();
244 
245  //calculate part of image which fits in bounds
246  int leftClip = 0;
247  int topClip = 0;
248 
249  //calculate left crop
250  switch ( mPictureAnchor )
251  {
252  case UpperLeft:
253  case MiddleLeft:
254  case LowerLeft:
255  leftClip = 0;
256  break;
257  case UpperMiddle:
258  case Middle:
259  case LowerMiddle:
260  leftClip = ( imageRectPixels.width() - boundRectWidthPixels ) / 2;
261  break;
262  case UpperRight:
263  case MiddleRight:
264  case LowerRight:
265  leftClip = imageRectPixels.width() - boundRectWidthPixels;
266  break;
267  }
268 
269  //calculate top crop
270  switch ( mPictureAnchor )
271  {
272  case UpperLeft:
273  case UpperMiddle:
274  case UpperRight:
275  topClip = 0;
276  break;
277  case MiddleLeft:
278  case Middle:
279  case MiddleRight:
280  topClip = ( imageRectPixels.height() - boundRectHeightPixels ) / 2;
281  break;
282  case LowerLeft:
283  case LowerMiddle:
284  case LowerRight:
285  topClip = imageRectPixels.height() - boundRectHeightPixels;
286  break;
287  }
288 
289  return QRect( leftClip, topClip, boundRectWidthPixels, boundRectHeightPixels );
290 }
291 
293 {
295  const QgsExpressionContext* evalContext = context ? context : &scopedContext;
296 
297  QString source = mSourcePath;
298 
299  //data defined source set?
300  mHasExpressionError = false;
302  {
303  bool ok = false;
304  source = mDataDefinedProperties.valueAsString( QgsComposerObject::PictureSource, *evalContext, source, &ok );
305  if ( ok )
306  {
307  source = source.trimmed();
308  QgsDebugMsg( QString( "exprVal PictureSource:%1" ).arg( source ) );
309  }
310  else
311  {
312  mHasExpressionError = true;
313  source = QString();
314  QgsMessageLog::logMessage( tr( "Picture expression eval error" ) );
315  }
316  }
317 
318  loadPicture( source );
319 }
320 
321 void QgsComposerPicture::loadRemotePicture( const QString &url )
322 {
323  //remote location
324 
325  QgsNetworkContentFetcher fetcher;
326  //pause until HTML fetch
327  mLoaded = false;
328  fetcher.fetchContent( QUrl( url ) );
329  connect( &fetcher, SIGNAL( finished() ), this, SLOT( remotePictureLoaded() ) );
330 
331  while ( !mLoaded )
332  {
333  qApp->processEvents();
334  }
335 
336  QNetworkReply* reply = fetcher.reply();
337  if ( reply )
338  {
339  QImageReader imageReader( reply );
340  mImage = imageReader.read();
341  mMode = RASTER;
342  reply->deleteLater();
343  }
344  else
345  {
346  mMode = Unknown;
347  }
348 }
349 
350 void QgsComposerPicture::loadLocalPicture( const QString &path )
351 {
352  QFile pic;
353  pic.setFileName( path );
354 
355  if ( !pic.exists() )
356  {
357  mMode = Unknown;
358  }
359  else
360  {
361  QFileInfo sourceFileInfo( pic );
362  QString sourceFileSuffix = sourceFileInfo.suffix();
363  if ( sourceFileSuffix.compare( QLatin1String( "svg" ), Qt::CaseInsensitive ) == 0 )
364  {
365  //try to open svg
367  QColor fillColor = mDataDefinedProperties.valueAsColor( QgsComposerObject::PictureSvgBackgroundColor, context, mSvgFillColor );
368  QColor strokeColor = mDataDefinedProperties.valueAsColor( QgsComposerObject::PictureSvgStrokeColor, context, mSvgStrokeColor );
369  double strokeWidth = mDataDefinedProperties.valueAsDouble( QgsComposerObject::PictureSvgStrokeWidth, context, mSvgStrokeWidth );
370  const QByteArray &svgContent = QgsApplication::svgCache()->svgContent( pic.fileName(), rect().width(), fillColor, strokeColor, strokeWidth,
371  1.0 );
372  mSVG.load( svgContent );
373  if ( mSVG.isValid() )
374  {
375  mMode = SVG;
376  QRect viewBox = mSVG.viewBox(); //take width/height ratio from view box instead of default size
377  mDefaultSvgSize.setWidth( viewBox.width() );
378  mDefaultSvgSize.setHeight( viewBox.height() );
379  }
380  else
381  {
382  mMode = Unknown;
383  }
384  }
385  else
386  {
387  //try to open raster with QImageReader
388  QImageReader imageReader( pic.fileName() );
389  if ( imageReader.read( &mImage ) )
390  {
391  mMode = RASTER;
392  }
393  else
394  {
395  mMode = Unknown;
396  }
397  }
398  }
399 
400 }
401 
402 void QgsComposerPicture::remotePictureLoaded()
403 {
404  mLoaded = true;
405 }
406 
407 void QgsComposerPicture::updateMapRotation()
408 {
409  if ( !mRotationMap )
410  return;
411 
412  // take map rotation
413  double rotation = mRotationMap->mapRotation();
414 
415  // handle true north
416  switch ( mNorthMode )
417  {
418  case GridNorth:
419  break; // nothing to do
420 
421  case TrueNorth:
422  {
423  QgsPoint center = mRotationMap->currentMapExtent()->center();
424  QgsCoordinateReferenceSystem crs = mRotationMap->crs();
425 
426  try
427  {
428  double bearing = QgsBearingUtils::bearingTrueNorth( crs, center );
429  rotation += bearing;
430  }
431  catch ( QgsException& e )
432  {
433  Q_UNUSED( e );
434  QgsDebugMsg( QString( "Caught exception %1" ).arg( e.what() ) );
435  }
436  break;
437  }
438  }
439 
440  rotation += mNorthOffset;
441  setPictureRotation( rotation );
442 }
443 
444 void QgsComposerPicture::loadPicture( const QString &path )
445 {
446  if ( path.startsWith( QLatin1String( "http" ) ) )
447  {
448  //remote location
449  loadRemotePicture( path );
450  }
451  else
452  {
453  //local location
454  loadLocalPicture( path );
455  }
456  if ( mMode != Unknown ) //make sure we start with a new QImage
457  {
458  recalculateSize();
459  }
460  else if ( mHasExpressionError || !( path.isEmpty() ) )
461  {
462  //trying to load an invalid file or bad expression, show cross picture
463  mMode = SVG;
464  QString badFile( QStringLiteral( ":/images/composer/missing_image.svg" ) );
465  mSVG.load( badFile );
466  if ( mSVG.isValid() )
467  {
468  mMode = SVG;
469  QRect viewBox = mSVG.viewBox(); //take width/height ratio from view box instead of default size
470  mDefaultSvgSize.setWidth( viewBox.width() );
471  mDefaultSvgSize.setHeight( viewBox.height() );
472  recalculateSize();
473  }
474  }
475 
476  emit itemChanged();
477 }
478 
479 QRectF QgsComposerPicture::boundedImageRect( double deviceWidth, double deviceHeight )
480 {
481  double imageToDeviceRatio;
482  if ( mImage.width() / deviceWidth > mImage.height() / deviceHeight )
483  {
484  imageToDeviceRatio = deviceWidth / mImage.width();
485  double height = imageToDeviceRatio * mImage.height();
486  return QRectF( 0, 0, deviceWidth, height );
487  }
488  else
489  {
490  imageToDeviceRatio = deviceHeight / mImage.height();
491  double width = imageToDeviceRatio * mImage.width();
492  return QRectF( 0, 0, width, deviceHeight );
493  }
494 }
495 
496 QRectF QgsComposerPicture::boundedSVGRect( double deviceWidth, double deviceHeight )
497 {
498  double imageToSvgRatio;
499  if ( deviceWidth / mDefaultSvgSize.width() > deviceHeight / mDefaultSvgSize.height() )
500  {
501  imageToSvgRatio = deviceHeight / mDefaultSvgSize.height();
502  double width = mDefaultSvgSize.width() * imageToSvgRatio;
503  return QRectF( 0, 0, width, deviceHeight );
504  }
505  else
506  {
507  imageToSvgRatio = deviceWidth / mDefaultSvgSize.width();
508  double height = mDefaultSvgSize.height() * imageToSvgRatio;
509  return QRectF( 0, 0, deviceWidth, height );
510  }
511 }
512 
513 QSizeF QgsComposerPicture::pictureSize()
514 {
515  if ( mMode == SVG )
516  {
517  return mDefaultSvgSize;
518  }
519  else if ( mMode == RASTER )
520  {
521  return QSizeF( mImage.width(), mImage.height() );
522  }
523  else
524  {
525  return QSizeF( 0, 0 );
526  }
527 }
528 
529 #if 0
530 QRectF QgsComposerPicture::boundedSVGRect( double deviceWidth, double deviceHeight )
531 {
532  double imageToSvgRatio;
533  if ( deviceWidth / mDefaultSvgSize.width() < deviceHeight / mDefaultSvgSize.height() )
534  {
535  imageToSvgRatio = deviceWidth / mDefaultSvgSize.width();
536  double height = mDefaultSvgSize.height() * imageToSvgRatio;
537  return QRectF( 0, 0, deviceWidth, height );
538  }
539  else
540  {
541  imageToSvgRatio = deviceHeight / mDefaultSvgSize.height();
542  double width = mDefaultSvgSize.width() * imageToSvgRatio;
543  return QRectF( 0, 0, width, deviceHeight );
544  }
545 }
546 #endif //0
547 
548 void QgsComposerPicture::setSceneRect( const QRectF& rectangle )
549 {
550  QSizeF currentPictureSize = pictureSize();
551 
552  if ( mResizeMode == QgsComposerPicture::Clip )
553  {
554  QgsComposerItem::setSceneRect( rectangle );
555  mPictureWidth = rectangle.width();
556  mPictureHeight = rectangle.height();
557  }
558  else
559  {
560  QRectF newRect = rectangle;
561 
562  if ( mResizeMode == ZoomResizeFrame && !rect().isEmpty() && !( currentPictureSize.isEmpty() ) )
563  {
564  QSizeF targetImageSize;
565  if ( qgsDoubleNear( mPictureRotation, 0.0 ) )
566  {
567  targetImageSize = currentPictureSize;
568  }
569  else
570  {
571  //calculate aspect ratio of bounds of rotated image
572  QTransform tr;
573  tr.rotate( mPictureRotation );
574  QRectF rotatedBounds = tr.mapRect( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ) );
575  targetImageSize = QSizeF( rotatedBounds.width(), rotatedBounds.height() );
576  }
577 
578  //if height has changed more than width, then fix width and set height correspondingly
579  //else, do the opposite
580  if ( qAbs( rect().width() - rectangle.width() ) <
581  qAbs( rect().height() - rectangle.height() ) )
582  {
583  newRect.setHeight( targetImageSize.height() * newRect.width() / targetImageSize.width() );
584  }
585  else
586  {
587  newRect.setWidth( targetImageSize.width() * newRect.height() / targetImageSize.height() );
588  }
589  }
590  else if ( mResizeMode == FrameToImageSize )
591  {
592  if ( !( currentPictureSize.isEmpty() ) )
593  {
594  newRect.setWidth( currentPictureSize.width() * 25.4 / mComposition->printResolution() );
595  newRect.setHeight( currentPictureSize.height() * 25.4 / mComposition->printResolution() );
596  }
597  }
598 
599  //find largest scaling of picture with this rotation which fits in item
600  if ( mResizeMode == Zoom || mResizeMode == ZoomResizeFrame )
601  {
602  QRectF rotatedImageRect = QgsComposerUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ), newRect, mPictureRotation );
603  mPictureWidth = rotatedImageRect.width();
604  mPictureHeight = rotatedImageRect.height();
605  }
606  else
607  {
608  mPictureWidth = newRect.width();
609  mPictureHeight = newRect.height();
610  }
611 
613  emit itemChanged();
614  }
615 
616  if ( mMode == SVG && !mLoadingSvg )
617  {
618  mLoadingSvg = true;
619  refreshPicture();
620  mLoadingSvg = false;
621  }
622 }
623 
625 {
626  double oldRotation = mPictureRotation;
627  mPictureRotation = r;
628 
629  if ( mResizeMode == Zoom )
630  {
631  //find largest scaling of picture with this rotation which fits in item
632  QSizeF currentPictureSize = pictureSize();
633  QRectF rotatedImageRect = QgsComposerUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ), rect(), mPictureRotation );
634  mPictureWidth = rotatedImageRect.width();
635  mPictureHeight = rotatedImageRect.height();
636  update();
637  }
638  else if ( mResizeMode == ZoomResizeFrame )
639  {
640  QSizeF currentPictureSize = pictureSize();
641  QRectF oldRect = QRectF( pos().x(), pos().y(), rect().width(), rect().height() );
642 
643  //calculate actual size of image inside frame
644  QRectF rotatedImageRect = QgsComposerUtils::largestRotatedRectWithinBounds( QRectF( 0, 0, currentPictureSize.width(), currentPictureSize.height() ), rect(), oldRotation );
645 
646  //rotate image rect by new rotation and get bounding box
647  QTransform tr;
648  tr.rotate( mPictureRotation );
649  QRectF newRect = tr.mapRect( QRectF( 0, 0, rotatedImageRect.width(), rotatedImageRect.height() ) );
650 
651  //keep the center in the same location
652  newRect.moveCenter( oldRect.center() );
654  emit itemChanged();
655  }
656 
657  emit pictureRotationChanged( mPictureRotation );
658 }
659 
660 void QgsComposerPicture::setRotationMap( int composerMapId )
661 {
662  if ( !mComposition )
663  {
664  return;
665  }
666 
667  if ( composerMapId == -1 ) //disable rotation from map
668  {
669  disconnect( mRotationMap, SIGNAL( mapRotationChanged( double ) ), this, SLOT( updateMapRotation() ) );
670  disconnect( mRotationMap, SIGNAL( extentChanged() ), this, SLOT( updateMapRotation() ) );
671  mRotationMap = nullptr;
672  }
673 
674  const QgsComposerMap* map = mComposition->getComposerMapById( composerMapId );
675  if ( !map )
676  {
677  return;
678  }
679  if ( mRotationMap )
680  {
681  disconnect( mRotationMap, SIGNAL( mapRotationChanged( double ) ), this, SLOT( updateMapRotation() ) );
682  disconnect( mRotationMap, SIGNAL( extentChanged() ), this, SLOT( updateMapRotation() ) );
683  }
684  mPictureRotation = map->mapRotation();
685  connect( map, SIGNAL( mapRotationChanged( double ) ), this, SLOT( updateMapRotation() ) );
686  connect( map, SIGNAL( extentChanged() ), this, SLOT( updateMapRotation() ) );
687  mRotationMap = map;
688  updateMapRotation();
689  emit pictureRotationChanged( mPictureRotation );
690 }
691 
693 {
694  mResizeMode = mode;
696  || ( mode == QgsComposerPicture::Zoom && !qgsDoubleNear( mPictureRotation, 0.0 ) ) )
697  {
698  //call set scene rect to force item to resize to fit picture
699  recalculateSize();
700  }
701  update();
702 }
703 
705 {
706  //call set scene rect with current position/size, as this will trigger the
707  //picture item to recalculate its frame and image size
708  setSceneRect( QRectF( pos().x(), pos().y(), rect().width(), rect().height() ) );
709 }
710 
712 {
714  const QgsExpressionContext* evalContext = context ? context : &scopedContext;
715 
718  || property == QgsComposerObject::AllProperties )
719  {
720  refreshPicture( evalContext );
721  }
722 
723  QgsComposerItem::refreshDataDefinedProperty( property, evalContext );
724 }
725 
726 void QgsComposerPicture::setPicturePath( const QString &path )
727 {
728  mSourcePath = path;
729  refreshPicture();
730 }
731 
733 {
734  return mSourcePath;
735 }
736 
737 bool QgsComposerPicture::writeXml( QDomElement& elem, QDomDocument & doc ) const
738 {
739  if ( elem.isNull() )
740  {
741  return false;
742  }
743  QDomElement composerPictureElem = doc.createElement( QStringLiteral( "ComposerPicture" ) );
744  composerPictureElem.setAttribute( QStringLiteral( "file" ), mComposition->project()->writePath( mSourcePath ) );
745  composerPictureElem.setAttribute( QStringLiteral( "pictureWidth" ), QString::number( mPictureWidth ) );
746  composerPictureElem.setAttribute( QStringLiteral( "pictureHeight" ), QString::number( mPictureHeight ) );
747  composerPictureElem.setAttribute( QStringLiteral( "resizeMode" ), QString::number( static_cast< int >( mResizeMode ) ) );
748  composerPictureElem.setAttribute( QStringLiteral( "anchorPoint" ), QString::number( static_cast< int >( mPictureAnchor ) ) );
749  composerPictureElem.setAttribute( QStringLiteral( "svgFillColor" ), QgsSymbolLayerUtils::encodeColor( mSvgFillColor ) );
750  composerPictureElem.setAttribute( QStringLiteral( "svgBorderColor" ), QgsSymbolLayerUtils::encodeColor( mSvgStrokeColor ) );
751  composerPictureElem.setAttribute( QStringLiteral( "svgBorderWidth" ), QString::number( mSvgStrokeWidth ) );
752 
753  //rotation
754  composerPictureElem.setAttribute( QStringLiteral( "pictureRotation" ), QString::number( mPictureRotation ) );
755  if ( !mRotationMap )
756  {
757  composerPictureElem.setAttribute( QStringLiteral( "mapId" ), -1 );
758  }
759  else
760  {
761  composerPictureElem.setAttribute( QStringLiteral( "mapId" ), mRotationMap->id() );
762  }
763  composerPictureElem.setAttribute( QStringLiteral( "northMode" ), mNorthMode );
764  composerPictureElem.setAttribute( QStringLiteral( "northOffset" ), mNorthOffset );
765 
766  _writeXml( composerPictureElem, doc );
767  elem.appendChild( composerPictureElem );
768  return true;
769 }
770 
771 bool QgsComposerPicture::readXml( const QDomElement& itemElem, const QDomDocument& doc )
772 {
773  if ( itemElem.isNull() )
774  {
775  return false;
776  }
777 
778  mPictureWidth = itemElem.attribute( QStringLiteral( "pictureWidth" ), QStringLiteral( "10" ) ).toDouble();
779  mPictureHeight = itemElem.attribute( QStringLiteral( "pictureHeight" ), QStringLiteral( "10" ) ).toDouble();
780  mResizeMode = QgsComposerPicture::ResizeMode( itemElem.attribute( QStringLiteral( "resizeMode" ), QStringLiteral( "0" ) ).toInt() );
781  //when loading from xml, default to anchor point of middle to match pre 2.4 behavior
782  mPictureAnchor = static_cast< QgsComposerItem::ItemPositionMode >( itemElem.attribute( QStringLiteral( "anchorPoint" ), QString::number( QgsComposerItem::Middle ) ).toInt() );
783 
784  mSvgFillColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "svgFillColor" ), QgsSymbolLayerUtils::encodeColor( QColor( 255, 255, 255 ) ) ) );
785  mSvgStrokeColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "svgBorderColor" ), QgsSymbolLayerUtils::encodeColor( QColor( 0, 0, 0 ) ) ) );
786  mSvgStrokeWidth = itemElem.attribute( QStringLiteral( "svgBorderWidth" ), QStringLiteral( "0.2" ) ).toDouble();
787 
788  QDomNodeList composerItemList = itemElem.elementsByTagName( QStringLiteral( "ComposerItem" ) );
789  if ( !composerItemList.isEmpty() )
790  {
791  QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
792 
793  if ( !qgsDoubleNear( composerItemElem.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble(), 0.0 ) )
794  {
795  //in versions prior to 2.1 picture rotation was stored in the rotation attribute
796  mPictureRotation = composerItemElem.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble();
797  }
798 
799  _readXml( composerItemElem, doc );
800  }
801 
802  mDefaultSvgSize = QSize( 0, 0 );
803 
804  if ( itemElem.hasAttribute( QStringLiteral( "sourceExpression" ) ) )
805  {
806  //update pre 2.5 picture expression to use data defined expression
807  QString sourceExpression = itemElem.attribute( QStringLiteral( "sourceExpression" ), QLatin1String( "" ) );
808  QString useExpression = itemElem.attribute( QStringLiteral( "useExpression" ) );
809  bool expressionActive;
810  expressionActive = ( useExpression.compare( QLatin1String( "true" ), Qt::CaseInsensitive ) == 0 );
811 
813  }
814 
815  mSourcePath = mComposition ? mComposition->project()->readPath( itemElem.attribute( QStringLiteral( "file" ) ) )
816  : itemElem.attribute( QStringLiteral( "file" ) );
817 
818  //picture rotation
819  if ( !qgsDoubleNear( itemElem.attribute( QStringLiteral( "pictureRotation" ), QStringLiteral( "0" ) ).toDouble(), 0.0 ) )
820  {
821  mPictureRotation = itemElem.attribute( QStringLiteral( "pictureRotation" ), QStringLiteral( "0" ) ).toDouble();
822  }
823 
824  //rotation map
825  mNorthMode = static_cast< NorthMode >( itemElem.attribute( QStringLiteral( "northMode" ), QStringLiteral( "0" ) ).toInt() );
826  mNorthOffset = itemElem.attribute( QStringLiteral( "northOffset" ), QStringLiteral( "0" ) ).toDouble();
827 
828  int rotationMapId = itemElem.attribute( QStringLiteral( "mapId" ), QStringLiteral( "-1" ) ).toInt();
829  if ( rotationMapId == -1 )
830  {
831  mRotationMap = nullptr;
832  }
833  else if ( mComposition )
834  {
835 
836  if ( mRotationMap )
837  {
838  disconnect( mRotationMap, SIGNAL( mapRotationChanged( double ) ), this, SLOT( updateMapRotation() ) );
839  disconnect( mRotationMap, SIGNAL( extentChanged() ), this, SLOT( updateMapRotation() ) );
840  }
841  mRotationMap = mComposition->getComposerMapById( rotationMapId );
842  connect( mRotationMap, SIGNAL( mapRotationChanged( double ) ), this, SLOT( updateMapRotation() ) );
843  connect( mRotationMap, SIGNAL( extentChanged() ), this, SLOT( updateMapRotation() ) );
844  }
845 
846  refreshPicture();
847 
848  emit itemChanged();
849  return true;
850 }
851 
853 {
854  if ( !mRotationMap )
855  {
856  return -1;
857  }
858  else
859  {
860  return mRotationMap->id();
861  }
862 }
863 
865 {
866  mNorthMode = mode;
867  updateMapRotation();
868 }
869 
871 {
872  mNorthOffset = offset;
873  updateMapRotation();
874 }
875 
877 {
878  mPictureAnchor = anchor;
879  update();
880 }
881 
882 void QgsComposerPicture::setSvgFillColor( const QColor& color )
883 {
884  mSvgFillColor = color;
885  refreshPicture();
886 }
887 
888 void QgsComposerPicture::setSvgStrokeColor( const QColor& color )
889 {
890  mSvgStrokeColor = color;
891  refreshPicture();
892 }
893 
895 {
896  mSvgStrokeWidth = width;
897  refreshPicture();
898 }
void setProperty(int key, const QgsProperty &property)
Adds a property to the collection and takes ownership of it.
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...
QColor valueAsColor(int key, const QgsExpressionContext &context, const QColor &defaultColor=QColor(), bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a color...
QgsPoint center() const
Center point of the rectangle.
Definition: qgsrectangle.h:222
void setSvgStrokeWidth(double width)
Sets the stroke width used for parametrized SVG files.
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.
double valueAsDouble(int key, const QgsExpressionContext &context, double defaultValue=0.0, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a double...
#define QgsDebugMsg(str)
Definition: qgslogger.h:36
void itemChanged()
Emitted when the item changes.
QgsComposerPicture(QgsComposition *composition)
int printResolution() const
A item that forms part of a map composition.
QString writePath(const QString &filename) const
Prepare a filename to save it to the project file.
static QgsProperty fromExpression(const QString &expression, bool isActive=true)
Returns a new ExpressionBasedProperty created from the specified expression.
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:198
DataDefinedProperty
Data defined properties for different item types.
QByteArray svgContent(const QString &file, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor)
Get SVG content.
bool isActive(int key) const override
Returns true if the collection contains an active property with the specified key.
A composer class that displays svg files or raster format (jpg, png, ...)
QString what() const
Definition: qgsexception.h:38
void setSvgStrokeColor(const QColor &color)
Sets the stroke color used for parametrized SVG files.
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)
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.
QgsPropertyCollection mDataDefinedProperties
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:36
Graphics scene for map printing.
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.
QgsComposition * mComposition
QString valueAsString(int key, const QgsExpressionContext &context, const QString &defaultString=QString(), bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a string...
QgsCoordinateReferenceSystem crs() const
Returns coordinate reference system used for rendering the map.
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 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...
QString readPath(const QString &filename) const
Turn filename read from the project file to an absolute path.
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.