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