QGIS API Documentation  2.2.0-Valmiera
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgscomposeritem.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscomposeritem.cpp
3  -------------------
4  begin : January 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 #include <QWidget>
18 #include <QDomNode>
19 #include <QFile>
20 #include <QGraphicsLineItem>
21 #include <QGraphicsScene>
22 #include <QGraphicsSceneMouseEvent>
23 #include <QGraphicsView>
24 #include <QPainter>
25 #include <QUuid>
26 #include <QGraphicsEffect>
27 
28 #include "qgsproject.h"
29 
30 #include "qgscomposition.h"
31 #include "qgscomposeritem.h"
32 #include "qgscomposerframe.h"
33 
34 #include <limits>
35 #include "qgsapplication.h"
36 #include "qgsrectangle.h" //just for debugging
37 #include "qgslogger.h"
38 #include "qgssymbollayerv2utils.h" //for pointOnLineWithDistance
39 #include "qgsmaprenderer.h" //for getCompositionMode
40 
41 #include <cmath>
42 
43 #define FONT_WORKAROUND_SCALE 10 //scale factor for upscaling fontsize and downscaling painter
44 
45 #ifndef M_DEG2RAD
46 #define M_DEG2RAD 0.0174532925
47 #endif
48 
49 QgsComposerItem::QgsComposerItem( QgsComposition* composition, bool manageZValue )
50  : QObject( 0 )
51  , QGraphicsRectItem( 0 )
52  , mComposition( composition )
53  , mBoundingResizeRectangle( 0 )
54  , mHAlignSnapItem( 0 )
55  , mVAlignSnapItem( 0 )
56  , mFrame( false )
57  , mBackground( true )
58  , mBackgroundColor( QColor( 255, 255, 255, 255 ) )
59  , mItemPositionLocked( false )
60  , mLastValidViewScaleFactor( -1 )
61  , mItemRotation( 0 )
62  , mBlendMode( QPainter::CompositionMode_SourceOver )
63  , mEffectsEnabled( true )
64  , mTransparency( 0 )
65  , mLastUsedPositionMode( UpperLeft )
66  , mId( "" )
67  , mUuid( QUuid::createUuid().toString() )
68 {
69  init( manageZValue );
70 }
71 
72 QgsComposerItem::QgsComposerItem( qreal x, qreal y, qreal width, qreal height, QgsComposition* composition, bool manageZValue )
73  : QObject( 0 )
74  , QGraphicsRectItem( 0, 0, width, height, 0 )
75  , mComposition( composition )
76  , mBoundingResizeRectangle( 0 )
77  , mHAlignSnapItem( 0 )
78  , mVAlignSnapItem( 0 )
79  , mFrame( false )
80  , mBackground( true )
81  , mBackgroundColor( QColor( 255, 255, 255, 255 ) )
82  , mItemPositionLocked( false )
83  , mLastValidViewScaleFactor( -1 )
84  , mItemRotation( 0 )
85  , mBlendMode( QPainter::CompositionMode_SourceOver )
86  , mEffectsEnabled( true )
87  , mTransparency( 0 )
88  , mLastUsedPositionMode( UpperLeft )
89  , mId( "" )
90  , mUuid( QUuid::createUuid().toString() )
91 {
92  init( manageZValue );
93  setPos( x, y );
94 }
95 
96 void QgsComposerItem::init( bool manageZValue )
97 {
98  setFlag( QGraphicsItem::ItemIsSelectable, true );
99  //set default pen and brush
100  setBrush( QBrush( QColor( 255, 255, 255, 255 ) ) );
101  QPen defaultPen( QColor( 0, 0, 0 ) );
102  defaultPen.setWidthF( 0.3 );
103  defaultPen.setJoinStyle( Qt::MiterJoin );
104  setPen( defaultPen );
105  //let z-Value be managed by composition
106  if ( mComposition && manageZValue )
107  {
108  mComposition->addItemToZList( this );
109  }
110 
111  // Setup composer effect
112  mEffect = new QgsComposerEffect();
113  setGraphicsEffect( mEffect );
114 }
115 
117 {
118  if ( mComposition )
119  {
121  }
122 
124  delete mEffect;
126 }
127 
129 {
130  QgsDebugMsg( "entered." );
132  update(); //to draw selection boxes
133 }
134 
135 bool QgsComposerItem::writeSettings( void ) { return true; }
136 
137 bool QgsComposerItem::readSettings( void ) { return true; }
138 
139 bool QgsComposerItem::removeSettings( void ) { return true; }
140 
141 bool QgsComposerItem::_writeXML( QDomElement& itemElem, QDomDocument& doc ) const
142 {
143  if ( itemElem.isNull() )
144  {
145  return false;
146  }
147 
148  QDomElement composerItemElem = doc.createElement( "ComposerItem" );
149 
150  //frame
151  if ( mFrame )
152  {
153  composerItemElem.setAttribute( "frame", "true" );
154  }
155  else
156  {
157  composerItemElem.setAttribute( "frame", "false" );
158  }
159 
160  //frame
161  if ( mBackground )
162  {
163  composerItemElem.setAttribute( "background", "true" );
164  }
165  else
166  {
167  composerItemElem.setAttribute( "background", "false" );
168  }
169 
170  //scene rect
171  composerItemElem.setAttribute( "x", QString::number( pos().x() ) );
172  composerItemElem.setAttribute( "y", QString::number( pos().y() ) );
173  composerItemElem.setAttribute( "width", QString::number( rect().width() ) );
174  composerItemElem.setAttribute( "height", QString::number( rect().height() ) );
175  composerItemElem.setAttribute( "positionMode", QString::number(( int ) mLastUsedPositionMode ) );
176  composerItemElem.setAttribute( "zValue", QString::number( zValue() ) );
177  composerItemElem.setAttribute( "outlineWidth", QString::number( pen().widthF() ) );
178  composerItemElem.setAttribute( "itemRotation", QString::number( mItemRotation ) );
179  composerItemElem.setAttribute( "uuid", mUuid );
180  composerItemElem.setAttribute( "id", mId );
181  //position lock for mouse moves/resizes
182  if ( mItemPositionLocked )
183  {
184  composerItemElem.setAttribute( "positionLock", "true" );
185  }
186  else
187  {
188  composerItemElem.setAttribute( "positionLock", "false" );
189  }
190 
191  composerItemElem.setAttribute( "lastValidViewScaleFactor", QString::number( mLastValidViewScaleFactor ) );
192 
193 
194  //frame color
195  QDomElement frameColorElem = doc.createElement( "FrameColor" );
196  QColor frameColor = pen().color();
197  frameColorElem.setAttribute( "red", QString::number( frameColor.red() ) );
198  frameColorElem.setAttribute( "green", QString::number( frameColor.green() ) );
199  frameColorElem.setAttribute( "blue", QString::number( frameColor.blue() ) );
200  frameColorElem.setAttribute( "alpha", QString::number( frameColor.alpha() ) );
201  composerItemElem.appendChild( frameColorElem );
202 
203  //background color
204  QDomElement bgColorElem = doc.createElement( "BackgroundColor" );
205  QColor bgColor = brush().color();
206  bgColorElem.setAttribute( "red", QString::number( bgColor.red() ) );
207  bgColorElem.setAttribute( "green", QString::number( bgColor.green() ) );
208  bgColorElem.setAttribute( "blue", QString::number( bgColor.blue() ) );
209  bgColorElem.setAttribute( "alpha", QString::number( bgColor.alpha() ) );
210  composerItemElem.appendChild( bgColorElem );
211 
212  //blend mode
213  composerItemElem.setAttribute( "blendMode", QgsMapRenderer::getBlendModeEnum( mBlendMode ) );
214 
215  //transparency
216  composerItemElem.setAttribute( "transparency", QString::number( mTransparency ) );
217 
218  itemElem.appendChild( composerItemElem );
219 
220  return true;
221 }
222 
223 bool QgsComposerItem::_readXML( const QDomElement& itemElem, const QDomDocument& doc )
224 {
225  Q_UNUSED( doc );
226  if ( itemElem.isNull() )
227  {
228  return false;
229  }
230 
231  //rotation
232  if ( itemElem.attribute( "itemRotation", "0" ).toDouble() != 0 )
233  {
234  setItemRotation( itemElem.attribute( "itemRotation", "0" ).toDouble() );
235  }
236 
237  //uuid
238  mUuid = itemElem.attribute( "uuid", QUuid::createUuid().toString() );
239 
240  // temporary for groups imported from templates
241  mTemplateUuid = itemElem.attribute( "templateUuid" );
242 
243  //id
244  QString id = itemElem.attribute( "id", "" );
245  setId( id );
246 
247  //frame
248  QString frame = itemElem.attribute( "frame" );
249  if ( frame.compare( "true", Qt::CaseInsensitive ) == 0 )
250  {
251  mFrame = true;
252  }
253  else
254  {
255  mFrame = false;
256  }
257 
258  //frame
259  QString background = itemElem.attribute( "background" );
260  if ( background.compare( "true", Qt::CaseInsensitive ) == 0 )
261  {
262  mBackground = true;
263  }
264  else
265  {
266  mBackground = false;
267  }
268 
269  //position lock for mouse moves/resizes
270  QString positionLock = itemElem.attribute( "positionLock" );
271  if ( positionLock.compare( "true", Qt::CaseInsensitive ) == 0 )
272  {
273  setPositionLock( true );
274  }
275  else
276  {
277  setPositionLock( false );
278  }
279 
280  //position
281  double x, y, width, height;
282  bool xOk, yOk, widthOk, heightOk, positionModeOK;
283 
284  x = itemElem.attribute( "x" ).toDouble( &xOk );
285  y = itemElem.attribute( "y" ).toDouble( &yOk );
286  width = itemElem.attribute( "width" ).toDouble( &widthOk );
287  height = itemElem.attribute( "height" ).toDouble( &heightOk );
288  mLastUsedPositionMode = ( ItemPositionMode )itemElem.attribute( "positionMode" ).toInt( &positionModeOK );
289  if ( !positionModeOK )
290  {
292  }
293 
294  if ( !xOk || !yOk || !widthOk || !heightOk )
295  {
296  return false;
297  }
298 
299  mLastValidViewScaleFactor = itemElem.attribute( "lastValidViewScaleFactor", "-1" ).toDouble();
300 
301  setSceneRect( QRectF( x, y, width, height ) );
302  setZValue( itemElem.attribute( "zValue" ).toDouble() );
303 
304  //pen
305  QDomNodeList frameColorList = itemElem.elementsByTagName( "FrameColor" );
306  if ( frameColorList.size() > 0 )
307  {
308  QDomElement frameColorElem = frameColorList.at( 0 ).toElement();
309  bool redOk, greenOk, blueOk, alphaOk, widthOk;
310  int penRed, penGreen, penBlue, penAlpha;
311  double penWidth;
312 
313  penWidth = itemElem.attribute( "outlineWidth" ).toDouble( &widthOk );
314  penRed = frameColorElem.attribute( "red" ).toDouble( &redOk );
315  penGreen = frameColorElem.attribute( "green" ).toDouble( &greenOk );
316  penBlue = frameColorElem.attribute( "blue" ).toDouble( &blueOk );
317  penAlpha = frameColorElem.attribute( "alpha" ).toDouble( &alphaOk );
318  if ( redOk && greenOk && blueOk && alphaOk && widthOk )
319  {
320  QPen framePen( QColor( penRed, penGreen, penBlue, penAlpha ) );
321  framePen.setWidthF( penWidth );
322  framePen.setJoinStyle( Qt::MiterJoin );
323  setPen( framePen );
324  }
325  }
326 
327  //brush
328  QDomNodeList bgColorList = itemElem.elementsByTagName( "BackgroundColor" );
329  if ( bgColorList.size() > 0 )
330  {
331  QDomElement bgColorElem = bgColorList.at( 0 ).toElement();
332  bool redOk, greenOk, blueOk, alphaOk;
333  int bgRed, bgGreen, bgBlue, bgAlpha;
334  bgRed = bgColorElem.attribute( "red" ).toDouble( &redOk );
335  bgGreen = bgColorElem.attribute( "green" ).toDouble( &greenOk );
336  bgBlue = bgColorElem.attribute( "blue" ).toDouble( &blueOk );
337  bgAlpha = bgColorElem.attribute( "alpha" ).toDouble( &alphaOk );
338  if ( redOk && greenOk && blueOk && alphaOk )
339  {
340  QColor brushColor( bgRed, bgGreen, bgBlue, bgAlpha );
341  setBackgroundColor( brushColor );
342  }
343  }
344 
345  //blend mode
346  setBlendMode( QgsMapRenderer::getCompositionMode(( QgsMapRenderer::BlendMode ) itemElem.attribute( "blendMode", "0" ).toUInt() ) );
347 
348  //transparency
349  setTransparency( itemElem.attribute( "transparency" , "0" ).toInt() );
350 
351  return true;
352 }
353 
354 void QgsComposerItem::setFrameEnabled( bool drawFrame )
355 {
356  mFrame = drawFrame;
357  emit frameChanged();
358 }
359 
361 {
362  QPen itemPen = pen();
363  if ( itemPen.widthF() == outlineWidth )
364  {
365  //no change
366  return;
367  }
368  itemPen.setWidthF( outlineWidth );
369  setPen( itemPen );
370  emit frameChanged();
371 }
372 
374 {
375  if ( !hasFrame() )
376  {
377  return 0;
378  }
379 
380  return pen().widthF() / 2.0;
381 }
382 
384 {
385  double frameBleed = estimatedFrameBleed();
386  return rect().adjusted( -frameBleed, -frameBleed, frameBleed, frameBleed );
387 }
388 
390 {
391  if ( mComposition )
392  {
393  mComposition->beginCommand( this, commandText, c );
394  }
395 }
396 
398 {
399  if ( mComposition )
400  {
402  }
403 }
404 
406 {
407  if ( mComposition )
408  {
410  }
411 }
412 
414 {
415 
416  if ( !mComposition )
417  {
418  return;
419  }
420 
422  {
423  double sizeLockSymbol = lockSymbolSize();
424 
425  if ( mItemPositionLocked )
426  {
427  //draw lock symbol at upper left edge. Use QImage to be independent of the graphic system
428  QString lockIconPath = QgsApplication::activeThemePath() + "/mIconLock.png";
429  if ( !QFile::exists( lockIconPath ) )
430  {
431  lockIconPath = QgsApplication::defaultThemePath() + "/mIconLock.png";
432  }
433 
434  QImage lockImage( lockIconPath );
435  if ( !lockImage.isNull() )
436  {
437  p->drawImage( QRectF( 0, 0, sizeLockSymbol, sizeLockSymbol ), lockImage, QRectF( 0, 0, lockImage.width(), lockImage.height() ) );
438  }
439  }
440  }
441 }
442 
443 void QgsComposerItem::drawFrame( QPainter* p )
444 {
445  if ( mFrame && p )
446  {
447  p->setPen( pen() );
448  p->setBrush( Qt::NoBrush );
449  p->setRenderHint( QPainter::Antialiasing, true );
450  p->drawRect( QRectF( 0, 0, rect().width(), rect().height() ) );
451  }
452 }
453 
455 {
456  mItemPositionLocked = lock;
457 }
458 
459 void QgsComposerItem::move( double dx, double dy )
460 {
461  QRectF newSceneRect( pos().x() + dx, pos().y() + dy, rect().width(), rect().height() );
462  setSceneRect( newSceneRect );
463 }
464 
465 void QgsComposerItem::setItemPosition( double x, double y, ItemPositionMode itemPoint )
466 {
467  double width = rect().width();
468  double height = rect().height();
469  setItemPosition( x, y, width, height, itemPoint );
470 }
471 
472 void QgsComposerItem::setItemPosition( double x, double y, double width, double height, ItemPositionMode itemPoint, bool posIncludesFrame )
473 {
474  double upperLeftX = x;
475  double upperLeftY = y;
476 
477  //store the item position mode
478  mLastUsedPositionMode = itemPoint;
479 
480  //adjust x-coordinate if placement is not done to a left point
481  if ( itemPoint == UpperMiddle || itemPoint == Middle || itemPoint == LowerMiddle )
482  {
483  upperLeftX -= width / 2.0;
484  }
485  else if ( itemPoint == UpperRight || itemPoint == MiddleRight || itemPoint == LowerRight )
486  {
487  upperLeftX -= width;
488  }
489 
490  //adjust y-coordinate if placement is not done to an upper point
491  if ( itemPoint == MiddleLeft || itemPoint == Middle || itemPoint == MiddleRight )
492  {
493  upperLeftY -= height / 2.0;
494  }
495  else if ( itemPoint == LowerLeft || itemPoint == LowerMiddle || itemPoint == LowerRight )
496  {
497  upperLeftY -= height;
498  }
499 
500  if ( posIncludesFrame )
501  {
502  //adjust position to account for frame size
503 
504  if ( mItemRotation == 0 )
505  {
506  upperLeftX += estimatedFrameBleed();
507  upperLeftY += estimatedFrameBleed();
508  }
509  else
510  {
511  //adjust position for item rotation
512  QLineF lineToItemOrigin = QLineF( 0, 0, estimatedFrameBleed(), estimatedFrameBleed() );
513  lineToItemOrigin.setAngle( -45 - mItemRotation );
514  upperLeftX += lineToItemOrigin.x2();
515  upperLeftY += lineToItemOrigin.y2();
516  }
517 
518  width -= 2 * estimatedFrameBleed();
519  height -= 2 * estimatedFrameBleed();
520  }
521 
522  setSceneRect( QRectF( upperLeftX, upperLeftY, width, height ) );
523 }
524 
525 void QgsComposerItem::setSceneRect( const QRectF& rectangle )
526 {
527  //setRect in item coordinates
528  double newWidth = rectangle.width();
529  double newHeight = rectangle.height();
530  double xTranslation = rectangle.x();
531  double yTranslation = rectangle.y();
532 
533  //correction if width and/or height are negative
534  if ( rectangle.width() < 0 )
535  {
536  newWidth = - rectangle.width();
537  xTranslation -= newWidth;
538  }
539 
540  if ( rectangle.height() < 0 )
541  {
542  newHeight = - rectangle.height();
543  yTranslation -= newHeight;
544  }
545 
546  QRectF newRect( 0, 0, newWidth, newHeight );
547  QGraphicsRectItem::setRect( newRect );
548  setPos( xTranslation, yTranslation );
549 
550  emit sizeChanged();
551 }
552 
554 {
555  if ( mBackground && p )
556  {
557  p->setBrush( brush() );//this causes a problem in atlas generation
558  p->setPen( Qt::NoPen );
559  p->setRenderHint( QPainter::Antialiasing, true );
560  p->drawRect( QRectF( 0, 0, rect().width(), rect().height() ) );
561  }
562 }
563 
564 void QgsComposerItem::setBackgroundColor( const QColor& backgroundColor )
565 {
567  setBrush( QBrush( mBackgroundColor, Qt::SolidPattern ) );
568 }
569 
570 void QgsComposerItem::setBlendMode( QPainter::CompositionMode blendMode )
571 {
573  // Update the composer effect to use the new blend mode
575 }
576 
577 void QgsComposerItem::setTransparency( int transparency )
578 {
580  // Set the QGraphicItem's opacity
581  setOpacity( 1. - ( transparency / 100. ) );
582 }
583 
584 void QgsComposerItem::setEffectsEnabled( bool effectsEnabled )
585 {
586  //enable or disable the QgsComposerEffect applied to this item
588  mEffect->setEnabled( effectsEnabled );
589 }
590 
591 void QgsComposerItem::drawText( QPainter* p, double x, double y, const QString& text, const QFont& font ) const
592 {
593  QFont textFont = scaledFontPixelSize( font );
594 
595  p->save();
596  p->setFont( textFont );
597  double scaleFactor = 1.0 / FONT_WORKAROUND_SCALE;
598  p->scale( scaleFactor, scaleFactor );
599  p->drawText( QPointF( x * FONT_WORKAROUND_SCALE, y * FONT_WORKAROUND_SCALE ), text );
600  p->restore();
601 }
602 
603 void QgsComposerItem::drawText( QPainter* p, const QRectF& rect, const QString& text, const QFont& font, Qt::AlignmentFlag halignment, Qt::AlignmentFlag valignment ) const
604 {
605  QFont textFont = scaledFontPixelSize( font );
606 
607  QRectF scaledRect( rect.x() * FONT_WORKAROUND_SCALE, rect.y() * FONT_WORKAROUND_SCALE,
608  rect.width() * FONT_WORKAROUND_SCALE, rect.height() * FONT_WORKAROUND_SCALE );
609 
610  p->save();
611  p->setFont( textFont );
612  double scaleFactor = 1.0 / FONT_WORKAROUND_SCALE;
613  p->scale( scaleFactor, scaleFactor );
614  p->drawText( scaledRect, halignment | valignment | Qt::TextWordWrap, text );
615  p->restore();
616 }
617 void QgsComposerItem::drawArrowHead( QPainter* p, double x, double y, double angle, double arrowHeadWidth ) const
618 {
619  if ( !p )
620  {
621  return;
622  }
623  double angleRad = angle / 180.0 * M_PI;
624  QPointF middlePoint( x, y );
625  //rotate both arrow points
626  QPointF p1 = QPointF( -arrowHeadWidth / 2.0, arrowHeadWidth );
627  QPointF p2 = QPointF( arrowHeadWidth / 2.0, arrowHeadWidth );
628 
629  QPointF p1Rotated, p2Rotated;
630  p1Rotated.setX( p1.x() * cos( angleRad ) + p1.y() * -sin( angleRad ) );
631  p1Rotated.setY( p1.x() * sin( angleRad ) + p1.y() * cos( angleRad ) );
632  p2Rotated.setX( p2.x() * cos( angleRad ) + p2.y() * -sin( angleRad ) );
633  p2Rotated.setY( p2.x() * sin( angleRad ) + p2.y() * cos( angleRad ) );
634 
635  QPolygonF arrowHeadPoly;
636  arrowHeadPoly << middlePoint;
637  arrowHeadPoly << QPointF( middlePoint.x() + p1Rotated.x(), middlePoint.y() + p1Rotated.y() );
638  arrowHeadPoly << QPointF( middlePoint.x() + p2Rotated.x(), middlePoint.y() + p2Rotated.y() );
639 
640  p->save();
641 
642  QPen arrowPen = p->pen();
643  arrowPen.setJoinStyle( Qt::RoundJoin );
644  QBrush arrowBrush = p->brush();
645  arrowBrush.setStyle( Qt::SolidPattern );
646  p->setPen( arrowPen );
647  p->setBrush( arrowBrush );
648  arrowBrush.setStyle( Qt::SolidPattern );
649  p->drawPolygon( arrowHeadPoly );
650 
651  p->restore();
652 }
653 
654 double QgsComposerItem::textWidthMillimeters( const QFont& font, const QString& text ) const
655 {
656  QFont metricsFont = scaledFontPixelSize( font );
657  QFontMetricsF fontMetrics( metricsFont );
658  return ( fontMetrics.width( text ) / FONT_WORKAROUND_SCALE );
659 }
660 
661 double QgsComposerItem::fontHeightCharacterMM( const QFont& font, const QChar& c ) const
662 {
663  QFont metricsFont = scaledFontPixelSize( font );
664  QFontMetricsF fontMetrics( metricsFont );
665  return ( fontMetrics.boundingRect( c ).height() / FONT_WORKAROUND_SCALE );
666 }
667 
668 double QgsComposerItem::fontAscentMillimeters( const QFont& font ) const
669 {
670  QFont metricsFont = scaledFontPixelSize( font );
671  QFontMetricsF fontMetrics( metricsFont );
672  return ( fontMetrics.ascent() / FONT_WORKAROUND_SCALE );
673 }
674 
675 double QgsComposerItem::fontDescentMillimeters( const QFont& font ) const
676 {
677  QFont metricsFont = scaledFontPixelSize( font );
678  QFontMetricsF fontMetrics( metricsFont );
679  return ( fontMetrics.descent() / FONT_WORKAROUND_SCALE );
680 }
681 
682 double QgsComposerItem::pixelFontSize( double pointSize ) const
683 {
684  return ( pointSize * 0.3527 );
685 }
686 
687 QFont QgsComposerItem::scaledFontPixelSize( const QFont& font ) const
688 {
689  QFont scaledFont = font;
690  double pixelSize = pixelFontSize( font.pointSizeF() ) * FONT_WORKAROUND_SCALE + 0.5;
691  scaledFont.setPixelSize( pixelSize );
692  return scaledFont;
693 }
694 
695 double QgsComposerItem::angle( const QPointF& p1, const QPointF& p2 ) const
696 {
697  double xDiff = p2.x() - p1.x();
698  double yDiff = p2.y() - p1.y();
699  double length = sqrt( xDiff * xDiff + yDiff * yDiff );
700  if ( length <= 0 )
701  {
702  return 0;
703  }
704 
705  double angle = acos(( -yDiff * length ) / ( length * length ) ) * 180 / M_PI;
706  if ( xDiff < 0 )
707  {
708  return ( 360 - angle );
709  }
710  return angle;
711 }
712 
714 {
715  double result = -1;
716  if ( scene() )
717  {
718  QList<QGraphicsView*> viewList = scene()->views();
719  if ( viewList.size() > 0 ) //if not, probably this function was called from non-gui code
720  {
721  QGraphicsView* currentView = viewList.at( 0 );
722  if ( currentView->isVisible() )
723  {
724  result = currentView->transform().m11();
725  mLastValidViewScaleFactor = result;
726  }
727  }
728  }
729  return result;
730 }
731 
733 {
734  //size of symbol boxes depends on zoom level in composer view
735  double viewScaleFactor = horizontalViewScaleFactor();
736  double rectHandlerSize = 10.0 / viewScaleFactor;
737 
738  //make sure the boxes don't get too large
739  if ( rectHandlerSize > ( rect().width() / 3 ) )
740  {
741  rectHandlerSize = rect().width() / 3;
742  }
743  if ( rectHandlerSize > ( rect().height() / 3 ) )
744  {
745  rectHandlerSize = rect().height() / 3;
746  }
747  return rectHandlerSize;
748 }
749 
751 {
752  double lockSymbolSize = 20.0 / horizontalViewScaleFactor();
753 
754  if ( lockSymbolSize > ( rect().width() / 3 ) )
755  {
756  lockSymbolSize = rect().width() / 3;
757  }
758  if ( lockSymbolSize > ( rect().height() / 3 ) )
759  {
760  lockSymbolSize = rect().height() / 3;
761  }
762  return lockSymbolSize;
763 }
764 
766 {
767  //kept for api compatibility with QGIS 2.0
768  //remove after 2.0 series
769  setItemRotation( r, true );
770 }
771 
772 void QgsComposerItem::setItemRotation( double r, bool adjustPosition )
773 {
774  if ( adjustPosition )
775  {
776  //adjustPosition set, so shift the position of the item so that rotation occurs around item center
777  //create a line from the centrepoint of the rect() to its origin, in scene coordinates
778  QLineF refLine = QLineF( mapToScene( QPointF( rect().width() / 2.0, rect().height() / 2.0 ) ) , mapToScene( QPointF( 0 , 0 ) ) );
779  //rotate this line by the current rotation angle
780  refLine.setAngle( refLine.angle() - r + mItemRotation );
781  //get new end point of line - this is the new item position
782  QPointF rotatedReferencePoint = refLine.p2();
783  setPos( rotatedReferencePoint );
784  emit sizeChanged();
785  }
786 
787  if ( r > 360 )
788  {
789  mItemRotation = (( int )r ) % 360;
790  }
791  else
792  {
793  mItemRotation = r;
794  }
795 
796  setTransformOriginPoint( 0, 0 );
798 
799  emit itemRotationChanged( r );
800  update();
801 }
802 
803 bool QgsComposerItem::imageSizeConsideringRotation( double& width, double& height ) const
804 {
805  //kept for api compatibility with QGIS 2.0, use item rotation
806  return imageSizeConsideringRotation( width, height, mItemRotation );
807 }
808 
809 QRectF QgsComposerItem::largestRotatedRectWithinBounds( QRectF originalRect, QRectF boundsRect, double rotation ) const
810 {
811  double originalWidth = originalRect.width();
812  double originalHeight = originalRect.height();
813  double boundsWidth = boundsRect.width();
814  double boundsHeight = boundsRect.height();
815  double ratioBoundsRect = boundsWidth / boundsHeight;
816 
817  //shortcut for some rotation values
818  if ( rotation == 0 || rotation == 90 || rotation == 180 || rotation == 270 )
819  {
820  double originalRatio = originalWidth / originalHeight;
821  double rectScale = originalRatio > ratioBoundsRect ? boundsWidth / originalWidth : boundsHeight / originalHeight;
822  double rectScaledWidth = rectScale * originalWidth;
823  double rectScaledHeight = rectScale * originalHeight;
824 
825  if ( rotation == 0 || rotation == 180 )
826  {
827  return QRectF(( boundsWidth - rectScaledWidth ) / 2.0, ( boundsHeight - rectScaledHeight ) / 2.0, rectScaledWidth, rectScaledHeight );
828  }
829  else
830  {
831  return QRectF(( boundsWidth - rectScaledHeight ) / 2.0, ( boundsHeight - rectScaledWidth ) / 2.0, rectScaledHeight, rectScaledWidth );
832  }
833  }
834 
835  //convert angle to radians and flip
836  double angleRad = -rotation * M_DEG2RAD;
837  double cosAngle = cos( angleRad );
838  double sinAngle = sin( angleRad );
839 
840  //calculate size of bounds of rotated rectangle
841  double widthBoundsRotatedRect = originalWidth * fabs( cosAngle ) + originalHeight * fabs( sinAngle );
842  double heightBoundsRotatedRect = originalHeight * fabs( cosAngle ) + originalWidth * fabs( sinAngle );
843 
844  //compare ratio of rotated rect with bounds rect and calculate scaling of rotated
845  //rect to fit within bounds
846  double ratioBoundsRotatedRect = widthBoundsRotatedRect / heightBoundsRotatedRect;
847  double rectScale = ratioBoundsRotatedRect > ratioBoundsRect ? boundsWidth / widthBoundsRotatedRect : boundsHeight / heightBoundsRotatedRect;
848  double rectScaledWidth = rectScale * originalWidth;
849  double rectScaledHeight = rectScale * originalHeight;
850 
851  //now calculate offset so that rotated rectangle is centered within bounds
852  //first calculate min x and y coordinates
853  double currentCornerX = 0;
854  double minX = 0;
855  currentCornerX += rectScaledWidth * cosAngle;
856  minX = minX < currentCornerX ? minX : currentCornerX;
857  currentCornerX += rectScaledHeight * sinAngle;
858  minX = minX < currentCornerX ? minX : currentCornerX;
859  currentCornerX -= rectScaledWidth * cosAngle;
860  minX = minX < currentCornerX ? minX : currentCornerX;
861 
862  double currentCornerY = 0;
863  double minY = 0;
864  currentCornerY -= rectScaledWidth * sinAngle;
865  minY = minY < currentCornerY ? minY : currentCornerY;
866  currentCornerY += rectScaledHeight * cosAngle;
867  minY = minY < currentCornerY ? minY : currentCornerY;
868  currentCornerY += rectScaledWidth * sinAngle;
869  minY = minY < currentCornerY ? minY : currentCornerY;
870 
871  //now calculate offset position of rotated rectangle
872  double offsetX = ratioBoundsRotatedRect > ratioBoundsRect ? 0 : ( boundsWidth - rectScale * widthBoundsRotatedRect ) / 2.0;
873  offsetX += fabs( minX );
874  double offsetY = ratioBoundsRotatedRect > ratioBoundsRect ? ( boundsHeight - rectScale * heightBoundsRotatedRect ) / 2.0 : 0;
875  offsetY += fabs( minY );
876 
877  return QRectF( offsetX, offsetY, rectScaledWidth, rectScaledHeight );
878 }
879 
880 bool QgsComposerItem::imageSizeConsideringRotation( double& width, double& height, double rotation ) const
881 {
882  if ( qAbs( rotation ) <= 0.0 ) //width and height stays the same if there is no rotation
883  {
884  return true;
885  }
886 
887  if ( qgsDoubleNear( qAbs( rotation ), 90 ) || qgsDoubleNear( qAbs( rotation ), 270 ) )
888  {
889  double tmp = width;
890  width = height;
891  height = tmp;
892  return true;
893  }
894 
895  double x1 = 0;
896  double y1 = 0;
897  double x2 = width;
898  double y2 = 0;
899  double x3 = width;
900  double y3 = height;
901  double x4 = 0;
902  double y4 = height;
903  double midX = width / 2.0;
904  double midY = height / 2.0;
905 
906  if ( !cornerPointOnRotatedAndScaledRect( x1, y1, width, height, rotation ) )
907  {
908  return false;
909  }
910  if ( !cornerPointOnRotatedAndScaledRect( x2, y2, width, height, rotation ) )
911  {
912  return false;
913  }
914  if ( !cornerPointOnRotatedAndScaledRect( x3, y3, width, height, rotation ) )
915  {
916  return false;
917  }
918  if ( !cornerPointOnRotatedAndScaledRect( x4, y4, width, height, rotation ) )
919  {
920  return false;
921  }
922 
923 
924  //assume points 1 and 3 are on the rectangle boundaries. Calculate 2 and 4.
925  double distM1 = sqrt(( x1 - midX ) * ( x1 - midX ) + ( y1 - midY ) * ( y1 - midY ) );
926  QPointF p2 = QgsSymbolLayerV2Utils::pointOnLineWithDistance( QPointF( midX, midY ), QPointF( x2, y2 ), distM1 );
927 
928  if ( p2.x() < width && p2.x() > 0 && p2.y() < height && p2.y() > 0 )
929  {
930  width = sqrt(( p2.x() - x1 ) * ( p2.x() - x1 ) + ( p2.y() - y1 ) * ( p2.y() - y1 ) );
931  height = sqrt(( x3 - p2.x() ) * ( x3 - p2.x() ) + ( y3 - p2.y() ) * ( y3 - p2.y() ) );
932  return true;
933  }
934 
935  //else assume that points 2 and 4 are on the rectangle boundaries. Calculate 1 and 3
936  double distM2 = sqrt(( x2 - midX ) * ( x2 - midX ) + ( y2 - midY ) * ( y2 - midY ) );
937  QPointF p1 = QgsSymbolLayerV2Utils::pointOnLineWithDistance( QPointF( midX, midY ), QPointF( x1, y1 ), distM2 );
938  QPointF p3 = QgsSymbolLayerV2Utils::pointOnLineWithDistance( QPointF( midX, midY ), QPointF( x3, y3 ), distM2 );
939  width = sqrt(( x2 - p1.x() ) * ( x2 - p1.x() ) + ( y2 - p1.y() ) * ( y2 - p1.y() ) );
940  height = sqrt(( p3.x() - x2 ) * ( p3.x() - x2 ) + ( p3.y() - y2 ) * ( p3.y() - y2 ) );
941  return true;
942 }
943 
944 bool QgsComposerItem::cornerPointOnRotatedAndScaledRect( double& x, double& y, double width, double height ) const
945 {
946  //kept for api compatibility with QGIS 2.0, use item rotation
947  return cornerPointOnRotatedAndScaledRect( x, y, width, height, mItemRotation );
948 }
949 
950 bool QgsComposerItem::cornerPointOnRotatedAndScaledRect( double& x, double& y, double width, double height, double rotation ) const
951 {
952  //first rotate point clockwise
953  double rotToRad = rotation * M_PI / 180.0;
954  QPointF midpoint( width / 2.0, height / 2.0 );
955  double xVector = x - midpoint.x();
956  double yVector = y - midpoint.y();
957  //double xRotated = cos(rotToRad) * xVector + sin(rotToRad) * yVector;
958  //double yRotated = -sin(rotToRad) * xVector + cos(rotToRad) * yVector;
959  double xRotated = cos( rotToRad ) * xVector - sin( rotToRad ) * yVector;
960  double yRotated = sin( rotToRad ) * xVector + cos( rotToRad ) * yVector;
961 
962  //create line from midpoint to rotated point
963  QLineF line( midpoint.x(), midpoint.y(), midpoint.x() + xRotated, midpoint.y() + yRotated );
964 
965  //intersect with all four borders and return result
966  QList<QLineF> borders;
967  borders << QLineF( 0, 0, width, 0 );
968  borders << QLineF( width, 0, width, height );
969  borders << QLineF( width, height, 0, height );
970  borders << QLineF( 0, height, 0, 0 );
971 
972  QList<QLineF>::const_iterator it = borders.constBegin();
973  QPointF intersectionPoint;
974 
975  for ( ; it != borders.constEnd(); ++it )
976  {
977  if ( line.intersect( *it, &intersectionPoint ) == QLineF::BoundedIntersection )
978  {
979  x = intersectionPoint.x();
980  y = intersectionPoint.y();
981  return true;
982  }
983  }
984  return false;
985 }
986 
987 void QgsComposerItem::sizeChangedByRotation( double& width, double& height )
988 {
989  //kept for api compatibility with QGIS 2.0, use item rotation
990  return sizeChangedByRotation( width, height, mItemRotation );
991 }
992 
993 void QgsComposerItem::sizeChangedByRotation( double& width, double& height, double rotation )
994 {
995  if ( rotation == 0.0 )
996  {
997  return;
998  }
999 
1000  //vector to p1
1001  double x1 = -width / 2.0;
1002  double y1 = -height / 2.0;
1003  rotate( rotation, x1, y1 );
1004  //vector to p2
1005  double x2 = width / 2.0;
1006  double y2 = -height / 2.0;
1007  rotate( rotation, x2, y2 );
1008  //vector to p3
1009  double x3 = width / 2.0;
1010  double y3 = height / 2.0;
1011  rotate( rotation, x3, y3 );
1012  //vector to p4
1013  double x4 = -width / 2.0;
1014  double y4 = height / 2.0;
1015  rotate( rotation, x4, y4 );
1016 
1017  //double midpoint
1018  QPointF midpoint( width / 2.0, height / 2.0 );
1019 
1020  QPolygonF rotatedRectPoly;
1021  rotatedRectPoly << QPointF( midpoint.x() + x1, midpoint.y() + y1 );
1022  rotatedRectPoly << QPointF( midpoint.x() + x2, midpoint.y() + y2 );
1023  rotatedRectPoly << QPointF( midpoint.x() + x3, midpoint.y() + y3 );
1024  rotatedRectPoly << QPointF( midpoint.x() + x4, midpoint.y() + y4 );
1025  QRectF boundingRect = rotatedRectPoly.boundingRect();
1026  width = boundingRect.width();
1027  height = boundingRect.height();
1028 }
1029 
1030 void QgsComposerItem::rotate( double angle, double& x, double& y ) const
1031 {
1032  double rotToRad = angle * M_PI / 180.0;
1033  double xRot, yRot;
1034  xRot = x * cos( rotToRad ) - y * sin( rotToRad );
1035  yRot = x * sin( rotToRad ) + y * cos( rotToRad );
1036  x = xRot;
1037  y = yRot;
1038 }
1039 
1041 {
1042  if ( !mHAlignSnapItem )
1043  {
1044  mHAlignSnapItem = new QGraphicsLineItem( 0 );
1045  mHAlignSnapItem->setPen( QPen( QColor( Qt::red ) ) );
1046  scene()->addItem( mHAlignSnapItem );
1047  mHAlignSnapItem->setZValue( 90 );
1048  }
1049  return mHAlignSnapItem;
1050 }
1051 
1053 {
1054  if ( !mVAlignSnapItem )
1055  {
1056  mVAlignSnapItem = new QGraphicsLineItem( 0 );
1057  mVAlignSnapItem->setPen( QPen( QColor( Qt::red ) ) );
1058  scene()->addItem( mVAlignSnapItem );
1059  mVAlignSnapItem->setZValue( 90 );
1060  }
1061  return mVAlignSnapItem;
1062 }
1063 
1065 {
1066  if ( mHAlignSnapItem )
1067  {
1068  scene()->removeItem( mHAlignSnapItem );
1069  delete mHAlignSnapItem;
1070  mHAlignSnapItem = 0;
1071  }
1072 }
1073 
1075 {
1076  if ( mVAlignSnapItem )
1077  {
1078  scene()->removeItem( mVAlignSnapItem );
1079  delete mVAlignSnapItem;
1080  mVAlignSnapItem = 0;
1081  }
1082 }
1083 
1085 {
1088 }
1089 
1091 {
1092  update();
1093 }
1094 
1095 void QgsComposerItem::setId( const QString& id )
1096 {
1097  setToolTip( id );
1098  mId = id;
1099 }