QGIS API Documentation  2.0.1-Dufour
 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 QgsComposerItem::QgsComposerItem( QgsComposition* composition, bool manageZValue )
46  : QObject( 0 )
47  , QGraphicsRectItem( 0 )
48  , mComposition( composition )
49  , mBoundingResizeRectangle( 0 )
50  , mHAlignSnapItem( 0 )
51  , mVAlignSnapItem( 0 )
52  , mFrame( false )
53  , mBackground( true )
54  , mBackgroundColor( QColor( 255, 255, 255, 255 ) )
55  , mItemPositionLocked( false )
56  , mLastValidViewScaleFactor( -1 )
57  , mRotation( 0 )
58  , mBlendMode( QPainter::CompositionMode_SourceOver )
59  , mEffectsEnabled( true )
60  , mTransparency( 0 )
61  , mLastUsedPositionMode( UpperLeft )
62  , mId( "" )
63  , mUuid( QUuid::createUuid().toString() )
64 {
65  init( manageZValue );
66 }
67 
68 QgsComposerItem::QgsComposerItem( qreal x, qreal y, qreal width, qreal height, QgsComposition* composition, bool manageZValue )
69  : QObject( 0 )
70  , QGraphicsRectItem( 0, 0, width, height, 0 )
71  , mComposition( composition )
72  , mBoundingResizeRectangle( 0 )
73  , mHAlignSnapItem( 0 )
74  , mVAlignSnapItem( 0 )
75  , mFrame( false )
76  , mBackground( true )
77  , mBackgroundColor( QColor( 255, 255, 255, 255 ) )
78  , mItemPositionLocked( false )
79  , mLastValidViewScaleFactor( -1 )
80  , mRotation( 0 )
81  , mBlendMode( QPainter::CompositionMode_SourceOver )
82  , mEffectsEnabled( true )
83  , mTransparency( 0 )
84  , mLastUsedPositionMode( UpperLeft )
85  , mId( "" )
86  , mUuid( QUuid::createUuid().toString() )
87 {
88  init( manageZValue );
89  QTransform t;
90  t.translate( x, y );
91  setTransform( t );
92 }
93 
94 void QgsComposerItem::init( bool manageZValue )
95 {
96  setFlag( QGraphicsItem::ItemIsSelectable, true );
97  setAcceptsHoverEvents( true );
98  //set default pen and brush
99  setBrush( QBrush( QColor( 255, 255, 255, 255 ) ) );
100  QPen defaultPen( QColor( 0, 0, 0 ) );
101  defaultPen.setWidthF( 0.3 );
102  setPen( defaultPen );
103  //let z-Value be managed by composition
104  if ( mComposition && manageZValue )
105  {
106  mComposition->addItemToZList( this );
107  }
108 
109  // Setup composer effect
110  mEffect = new QgsComposerEffect();
111  setGraphicsEffect( mEffect );
112 }
113 
115 {
116  if ( mComposition )
117  {
119  }
120 
122  delete mEffect;
124 }
125 
127 {
128  QgsDebugMsg( "entered." );
130  update(); //to draw selection boxes
131 }
132 
133 bool QgsComposerItem::writeSettings( void ) { return true; }
134 
135 bool QgsComposerItem::readSettings( void ) { return true; }
136 
137 bool QgsComposerItem::removeSettings( void ) { return true; }
138 
139 bool QgsComposerItem::_writeXML( QDomElement& itemElem, QDomDocument& doc ) const
140 {
141  if ( itemElem.isNull() )
142  {
143  return false;
144  }
145 
146  QDomElement composerItemElem = doc.createElement( "ComposerItem" );
147 
148  //frame
149  if ( mFrame )
150  {
151  composerItemElem.setAttribute( "frame", "true" );
152  }
153  else
154  {
155  composerItemElem.setAttribute( "frame", "false" );
156  }
157 
158  //frame
159  if ( mBackground )
160  {
161  composerItemElem.setAttribute( "background", "true" );
162  }
163  else
164  {
165  composerItemElem.setAttribute( "background", "false" );
166  }
167 
168  //scene rect
169  composerItemElem.setAttribute( "x", QString::number( transform().dx() ) );
170  composerItemElem.setAttribute( "y", QString::number( transform().dy() ) );
171  composerItemElem.setAttribute( "width", QString::number( rect().width() ) );
172  composerItemElem.setAttribute( "height", QString::number( rect().height() ) );
173  composerItemElem.setAttribute( "positionMode", QString::number(( int ) mLastUsedPositionMode ) );
174  composerItemElem.setAttribute( "zValue", QString::number( zValue() ) );
175  composerItemElem.setAttribute( "outlineWidth", QString::number( pen().widthF() ) );
176  composerItemElem.setAttribute( "rotation", QString::number( mRotation ) );
177  composerItemElem.setAttribute( "uuid", mUuid );
178  composerItemElem.setAttribute( "id", mId );
179  //position lock for mouse moves/resizes
180  if ( mItemPositionLocked )
181  {
182  composerItemElem.setAttribute( "positionLock", "true" );
183  }
184  else
185  {
186  composerItemElem.setAttribute( "positionLock", "false" );
187  }
188 
189  composerItemElem.setAttribute( "lastValidViewScaleFactor", QString::number( mLastValidViewScaleFactor ) );
190 
191 
192  //frame color
193  QDomElement frameColorElem = doc.createElement( "FrameColor" );
194  QColor frameColor = pen().color();
195  frameColorElem.setAttribute( "red", QString::number( frameColor.red() ) );
196  frameColorElem.setAttribute( "green", QString::number( frameColor.green() ) );
197  frameColorElem.setAttribute( "blue", QString::number( frameColor.blue() ) );
198  frameColorElem.setAttribute( "alpha", QString::number( frameColor.alpha() ) );
199  composerItemElem.appendChild( frameColorElem );
200 
201  //background color
202  QDomElement bgColorElem = doc.createElement( "BackgroundColor" );
203  QColor bgColor = brush().color();
204  bgColorElem.setAttribute( "red", QString::number( bgColor.red() ) );
205  bgColorElem.setAttribute( "green", QString::number( bgColor.green() ) );
206  bgColorElem.setAttribute( "blue", QString::number( bgColor.blue() ) );
207  bgColorElem.setAttribute( "alpha", QString::number( bgColor.alpha() ) );
208  composerItemElem.appendChild( bgColorElem );
209 
210  //blend mode
211  composerItemElem.setAttribute( "blendMode", QgsMapRenderer::getBlendModeEnum( mBlendMode ) );
212 
213  //transparency
214  composerItemElem.setAttribute( "transparency", QString::number( mTransparency ) );
215 
216  itemElem.appendChild( composerItemElem );
217 
218  return true;
219 }
220 
221 bool QgsComposerItem::_readXML( const QDomElement& itemElem, const QDomDocument& doc )
222 {
223  Q_UNUSED( doc );
224  if ( itemElem.isNull() )
225  {
226  return false;
227  }
228 
229  //rotation
230  mRotation = itemElem.attribute( "rotation", "0" ).toDouble();
231 
232  //uuid
233  mUuid = itemElem.attribute( "uuid", QUuid::createUuid().toString() );
234 
235  // temporary for groups imported from templates
236  mTemplateUuid = itemElem.attribute( "templateUuid" );
237 
238  //id
239  QString id = itemElem.attribute( "id", "" );
240  setId( id );
241 
242  //frame
243  QString frame = itemElem.attribute( "frame" );
244  if ( frame.compare( "true", Qt::CaseInsensitive ) == 0 )
245  {
246  mFrame = true;
247  }
248  else
249  {
250  mFrame = false;
251  }
252 
253  //frame
254  QString background = itemElem.attribute( "background" );
255  if ( background.compare( "true", Qt::CaseInsensitive ) == 0 )
256  {
257  mBackground = true;
258  }
259  else
260  {
261  mBackground = false;
262  }
263 
264  //position lock for mouse moves/resizes
265  QString positionLock = itemElem.attribute( "positionLock" );
266  if ( positionLock.compare( "true", Qt::CaseInsensitive ) == 0 )
267  {
268  mItemPositionLocked = true;
269  }
270  else
271  {
272  mItemPositionLocked = false;
273  }
274 
275  //position
276  double x, y, width, height;
277  bool xOk, yOk, widthOk, heightOk, positionModeOK;
278 
279  x = itemElem.attribute( "x" ).toDouble( &xOk );
280  y = itemElem.attribute( "y" ).toDouble( &yOk );
281  width = itemElem.attribute( "width" ).toDouble( &widthOk );
282  height = itemElem.attribute( "height" ).toDouble( &heightOk );
283  mLastUsedPositionMode = ( ItemPositionMode )itemElem.attribute( "positionMode" ).toInt( &positionModeOK );
284  if ( !positionModeOK )
285  {
287  }
288 
289  if ( !xOk || !yOk || !widthOk || !heightOk )
290  {
291  return false;
292  }
293 
294  mLastValidViewScaleFactor = itemElem.attribute( "lastValidViewScaleFactor", "-1" ).toDouble();
295 
296  setSceneRect( QRectF( x, y, width, height ) );
297  setZValue( itemElem.attribute( "zValue" ).toDouble() );
298 
299  //pen
300  QDomNodeList frameColorList = itemElem.elementsByTagName( "FrameColor" );
301  if ( frameColorList.size() > 0 )
302  {
303  QDomElement frameColorElem = frameColorList.at( 0 ).toElement();
304  bool redOk, greenOk, blueOk, alphaOk, widthOk;
305  int penRed, penGreen, penBlue, penAlpha;
306  double penWidth;
307 
308  penWidth = itemElem.attribute( "outlineWidth" ).toDouble( &widthOk );
309  penRed = frameColorElem.attribute( "red" ).toDouble( &redOk );
310  penGreen = frameColorElem.attribute( "green" ).toDouble( &greenOk );
311  penBlue = frameColorElem.attribute( "blue" ).toDouble( &blueOk );
312  penAlpha = frameColorElem.attribute( "alpha" ).toDouble( &alphaOk );
313  if ( redOk && greenOk && blueOk && alphaOk && widthOk )
314  {
315  QPen framePen( QColor( penRed, penGreen, penBlue, penAlpha ) );
316  framePen.setWidthF( penWidth );
317  setPen( framePen );
318  }
319  }
320 
321  //brush
322  QDomNodeList bgColorList = itemElem.elementsByTagName( "BackgroundColor" );
323  if ( bgColorList.size() > 0 )
324  {
325  QDomElement bgColorElem = bgColorList.at( 0 ).toElement();
326  bool redOk, greenOk, blueOk, alphaOk;
327  int bgRed, bgGreen, bgBlue, bgAlpha;
328  bgRed = bgColorElem.attribute( "red" ).toDouble( &redOk );
329  bgGreen = bgColorElem.attribute( "green" ).toDouble( &greenOk );
330  bgBlue = bgColorElem.attribute( "blue" ).toDouble( &blueOk );
331  bgAlpha = bgColorElem.attribute( "alpha" ).toDouble( &alphaOk );
332  if ( redOk && greenOk && blueOk && alphaOk )
333  {
334  QColor brushColor( bgRed, bgGreen, bgBlue, bgAlpha );
335  setBackgroundColor( brushColor );
336  }
337  }
338 
339  //blend mode
340  setBlendMode( QgsMapRenderer::getCompositionMode(( QgsMapRenderer::BlendMode ) itemElem.attribute( "blendMode", "0" ).toUInt() ) );
341 
342  //transparency
343  setTransparency( itemElem.attribute( "transparency" , "0" ).toInt() );
344 
345  return true;
346 }
347 
349 {
350  if ( mComposition )
351  {
352  mComposition->beginCommand( this, commandText, c );
353  }
354 }
355 
357 {
358  if ( mComposition )
359  {
361  }
362 }
363 
365 {
366  if ( mComposition )
367  {
369  }
370 }
371 
372 void QgsComposerItem::mouseMoveEvent( QGraphicsSceneMouseEvent * event )
373 {
374  if ( mItemPositionLocked )
375  {
376  return;
377  }
378 
379  if ( !isSelected() )
380  {
381  return;
382  }
383 
385  {
386  double diffX = event->lastScenePos().x() - mLastMouseEventPos.x();
387  double diffY = event->lastScenePos().y() - mLastMouseEventPos.y();
388 
389  changeItemRectangle( event->lastScenePos(), mMouseMoveStartPos, this, diffX, diffY, mBoundingResizeRectangle );
390  }
391  mLastMouseEventPos = event->lastScenePos();
392 }
393 
394 void QgsComposerItem::mousePressEvent( QGraphicsSceneMouseEvent * event )
395 {
396  if ( mItemPositionLocked )
397  {
398  return;
399  }
400 
401  if ( !isSelected() )
402  {
403  return;
404  }
405 
406  //set current position and type of mouse move action
407  mMouseMoveStartPos = event->lastScenePos();
408  mLastMouseEventPos = event->lastScenePos();
410 
411  //remove the old rubber band item if it is still there
413  {
414  scene()->removeItem( mBoundingResizeRectangle );
417  }
419 
420  //create and show bounding rectangle
421  mBoundingResizeRectangle = new QGraphicsRectItem( 0 );
422  scene()->addItem( mBoundingResizeRectangle );
423  mBoundingResizeRectangle->setRect( QRectF( 0, 0, rect().width(), rect().height() ) );
424  QTransform resizeTransform;
425  resizeTransform.translate( transform().dx(), transform().dy() );
426  mBoundingResizeRectangle->setTransform( resizeTransform );
427 
428  mBoundingResizeRectangle->setBrush( Qt::NoBrush );
429  mBoundingResizeRectangle->setPen( QPen( QColor( 0, 0, 0 ), 0 ) );
430  mBoundingResizeRectangle->setZValue( 90 );
431  mBoundingResizeRectangle->show();
432 }
433 
434 void QgsComposerItem::mouseReleaseEvent( QGraphicsSceneMouseEvent * event )
435 {
436 
437  if ( mItemPositionLocked )
438  {
439  return;
440  }
441 
442  if ( !isSelected() )
443  {
444  return;
445  }
446 
447  //delete frame rectangle
449  {
450  scene()->removeItem( mBoundingResizeRectangle );
453  }
454 
455  QPointF mouseMoveStopPoint = event->lastScenePos();
456  double diffX = mouseMoveStopPoint.x() - mMouseMoveStartPos.x();
457  double diffY = mouseMoveStopPoint.y() - mMouseMoveStartPos.y();
458 
459  //it was only a click
460  if ( qAbs( diffX ) < std::numeric_limits<double>::min() && qAbs( diffY ) < std::numeric_limits<double>::min() )
461  {
462  return;
463  }
464 
465  beginItemCommand( tr( "Change item position" ) );
466  changeItemRectangle( mouseMoveStopPoint, mMouseMoveStartPos, this, diffX, diffY, this );
467  endItemCommand();
468 
470 
471  //reset default action
473  setCursor( Qt::ArrowCursor );
474 }
475 
476 Qt::CursorShape QgsComposerItem::cursorForPosition( const QPointF& itemCoordPos )
477 {
478  QgsComposerItem::MouseMoveAction mouseAction = mouseMoveActionForPosition( itemCoordPos );
479  switch ( mouseAction )
480  {
481  case NoAction:
482  return Qt::ForbiddenCursor;
483  case MoveItem:
484  return Qt::SizeAllCursor;
485  case ResizeUp:
486  case ResizeDown:
487  return Qt::SizeVerCursor;
488  case ResizeLeft:
489  case ResizeRight:
490  return Qt::SizeHorCursor;
491  case ResizeLeftUp:
492  case ResizeRightDown:
493  return Qt::SizeFDiagCursor;
494  case ResizeRightUp:
495  case ResizeLeftDown:
496  return Qt::SizeBDiagCursor;
497  default:
498  return Qt::ArrowCursor;
499  }
500 }
501 
503 {
504 
505  //no action at all if item position is locked for mouse
506  if ( mItemPositionLocked )
507  {
509  }
510 
511  bool nearLeftBorder = false;
512  bool nearRightBorder = false;
513  bool nearLowerBorder = false;
514  bool nearUpperBorder = false;
515 
516  double borderTolerance = rectHandlerBorderTolerance();
517 
518  if ( itemCoordPos.x() < borderTolerance )
519  {
520  nearLeftBorder = true;
521  }
522  if ( itemCoordPos.y() < borderTolerance )
523  {
524  nearUpperBorder = true;
525  }
526  if ( itemCoordPos.x() > ( rect().width() - borderTolerance ) )
527  {
528  nearRightBorder = true;
529  }
530  if ( itemCoordPos.y() > ( rect().height() - borderTolerance ) )
531  {
532  nearLowerBorder = true;
533  }
534 
535  if ( nearLeftBorder && nearUpperBorder )
536  {
538  }
539  else if ( nearLeftBorder && nearLowerBorder )
540  {
542  }
543  else if ( nearRightBorder && nearUpperBorder )
544  {
546  }
547  else if ( nearRightBorder && nearLowerBorder )
548  {
550  }
551  else if ( nearLeftBorder )
552  {
554  }
555  else if ( nearRightBorder )
556  {
558  }
559  else if ( nearUpperBorder )
560  {
562  }
563  else if ( nearLowerBorder )
564  {
566  }
567 
568  return QgsComposerItem::MoveItem; //default
569 }
570 
571 void QgsComposerItem::changeItemRectangle( const QPointF& currentPosition,
572  const QPointF& mouseMoveStartPos,
573  const QGraphicsRectItem* originalItem,
574  double dx, double dy,
575  QGraphicsRectItem* changeItem )
576 {
577  Q_UNUSED( dx );
578  Q_UNUSED( dy );
579  if ( !changeItem || !originalItem || !mComposition )
580  {
581  return;
582  }
583 
584  //test if change item is a composer item. If so, prefer call to setSceneRect() instead of setTransform() and setRect()
585  QgsComposerItem* changeComposerItem = dynamic_cast<QgsComposerItem *>( changeItem );
586 
587  double mx = 0.0, my = 0.0, rx = 0.0, ry = 0.0;
588  QPointF snappedPosition = mComposition->snapPointToGrid( currentPosition );
589 
590  //snap to grid and align to other items
592  {
593  double alignX = 0;
594  double alignY = 0;
595  snappedPosition = mComposition->alignPos( snappedPosition, dynamic_cast<const QgsComposerItem*>( originalItem ), alignX, alignY );
596  if ( alignX != -1 )
597  {
598  QGraphicsLineItem* item = hAlignSnapItem();
599  item->setLine( QLineF( alignX, 0, alignX, mComposition->paperHeight() ) );
600  item->show();
601  }
602  else
603  {
605  }
606 
607  if ( alignY != -1 )
608  {
609  QGraphicsLineItem* item = vAlignSnapItem();
610  item->setLine( QLineF( 0, alignY, mComposition->paperWidth(), alignY ) );
611  item->show();
612  }
613  else
614  {
616  }
617  }
618 
619  double diffX = 0;
620  double diffY = 0;
621 
622  switch ( mCurrentMouseMoveAction )
623  {
624  //vertical resize
626  diffY = snappedPosition.y() - originalItem->transform().dy();
627  mx = 0; my = diffY; rx = 0; ry = -diffY;
628  break;
629 
631  diffY = snappedPosition.y() - ( originalItem->transform().dy() + originalItem->rect().height() );
632  mx = 0; my = 0; rx = 0; ry = diffY;
633  break;
634 
635  //horizontal resize
637  diffX = snappedPosition.x() - originalItem->transform().dx();
638  mx = diffX, my = 0; rx = -diffX; ry = 0;
639  break;
640 
642  diffX = snappedPosition.x() - ( originalItem->transform().dx() + originalItem->rect().width() );
643  mx = 0; my = 0; rx = diffX, ry = 0;
644  break;
645 
646  //diagonal resize
648  diffX = snappedPosition.x() - originalItem->transform().dx();
649  diffY = snappedPosition.y() - originalItem->transform().dy();
650  mx = diffX, my = diffY; rx = -diffX; ry = -diffY;
651  break;
652 
654  diffX = snappedPosition.x() - ( originalItem->transform().dx() + originalItem->rect().width() );
655  diffY = snappedPosition.y() - ( originalItem->transform().dy() + originalItem->rect().height() );
656  mx = 0; my = 0; rx = diffX, ry = diffY;
657  break;
658 
660  diffX = snappedPosition.x() - ( originalItem->transform().dx() + originalItem->rect().width() );
661  diffY = snappedPosition.y() - originalItem->transform().dy();
662  mx = 0; my = diffY, rx = diffX, ry = -diffY;
663  break;
664 
666  diffX = snappedPosition.x() - originalItem->transform().dx();
667  diffY = snappedPosition.y() - ( originalItem->transform().dy() + originalItem->rect().height() );
668  mx = diffX, my = 0; rx = -diffX; ry = diffY;
669  break;
670 
672  {
673  //calculate total move difference
674  double moveX = currentPosition.x() - mouseMoveStartPos.x();
675  double moveY = currentPosition.y() - mouseMoveStartPos.y();
676 
677  QPointF upperLeftPoint( originalItem->transform().dx() + moveX, originalItem->transform().dy() + moveY );
678  QPointF snappedLeftPoint = mComposition->snapPointToGrid( upperLeftPoint );
679 
680  if ( snappedLeftPoint != upperLeftPoint ) //don't do align snap if grid snap has been done
681  {
683  }
684  else if ( mComposition->alignmentSnap() ) //align item
685  {
686  double alignX = 0;
687  double alignY = 0;
688  snappedLeftPoint = mComposition->alignItem( dynamic_cast<const QgsComposerItem*>( originalItem ), alignX, alignY, moveX, moveY );
689  if ( alignX != -1 )
690  {
691  QGraphicsLineItem* item = hAlignSnapItem();
692  int numPages = mComposition->numPages();
693  double yLineCoord = 300; //default in case there is no single page
694  if ( numPages > 0 )
695  {
696  yLineCoord = mComposition->paperHeight() * numPages + mComposition->spaceBetweenPages() * ( numPages - 1 );
697  }
698  item->setLine( QLineF( alignX, 0, alignX, yLineCoord ) );
699  item->show();
700  }
701  else
702  {
704  }
705  if ( alignY != -1 )
706  {
707  QGraphicsLineItem* item = vAlignSnapItem();
708  item->setLine( QLineF( 0, alignY, mComposition->paperWidth(), alignY ) );
709  item->show();
710  }
711  else
712  {
714  }
715  }
716  double moveRectX = snappedLeftPoint.x() - originalItem->transform().dx();
717  double moveRectY = snappedLeftPoint.y() - originalItem->transform().dy();
718 
719  if ( !changeComposerItem )
720  {
721  QTransform moveTransform;
722  moveTransform.translate( originalItem->transform().dx() + moveRectX, originalItem->transform().dy() + moveRectY );
723  changeItem->setTransform( moveTransform );
724  }
725  else //for composer items, we prefer setSceneRect as subclasses can implement custom behaviour (e.g. item group)
726  {
727  changeComposerItem->setSceneRect( QRectF( originalItem->transform().dx() + moveRectX,
728  originalItem->transform().dy() + moveRectY,
729  originalItem->rect().width(), originalItem->rect().height() ) );
730  changeComposerItem->updateItem();
731  }
732  }
733  return;
735  break;
736  }
737 
738  if ( !changeComposerItem )
739  {
740  QTransform itemTransform;
741  itemTransform.translate( originalItem->transform().dx() + mx, originalItem->transform().dy() + my );
742  changeItem->setTransform( itemTransform );
743  QRectF itemRect( 0, 0, originalItem->rect().width() + rx, originalItem->rect().height() + ry );
744  changeItem->setRect( itemRect );
745  }
746  else //for composer items, we prefer setSceneRect as subclasses can implement custom behaviour (e.g. item group)
747  {
748  changeComposerItem->setSceneRect( QRectF( originalItem->transform().dx() + mx, originalItem->transform().dy() + my,
749  originalItem->rect().width() + rx, originalItem->rect().height() + ry ) );
750  changeComposerItem->updateItem();
751  }
752 }
753 
755 {
756  if ( !mComposition )
757  {
758  return;
759  }
760 
762  {
763  //size of symbol boxes depends on zoom level in composer view
764  double rectHandlerSize = rectHandlerBorderTolerance();
765  double sizeLockSymbol = lockSymbolSize();
766 
767  if ( mItemPositionLocked )
768  {
769  //draw lock symbol at upper left edge. Use QImage to be independent of the graphic system
770  QString lockIconPath = QgsApplication::activeThemePath() + "/mIconLock.png";
771  if ( !QFile::exists( lockIconPath ) )
772  {
773  lockIconPath = QgsApplication::defaultThemePath() + "/mIconLock.png";
774  }
775 
776  QImage lockImage( lockIconPath );
777  if ( !lockImage.isNull() )
778  {
779  p->drawImage( QRectF( 0, 0, sizeLockSymbol, sizeLockSymbol ), lockImage, QRectF( 0, 0, lockImage.width(), lockImage.height() ) );
780  }
781  }
782  else //draw blue squares
783  {
784  p->setPen( QColor( 50, 100, 120, 200 ) );
785  p->setBrush( QColor( 200, 200, 210, 120 ) );
786  p->drawRect( QRectF( 0, 0, rectHandlerSize, rectHandlerSize ) );
787  p->drawRect( QRectF( rect().width() - rectHandlerSize, 0, rectHandlerSize, rectHandlerSize ) );
788  p->drawRect( QRectF( rect().width() - rectHandlerSize, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) );
789  p->drawRect( QRectF( 0, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) );
790  }
791  }
792 }
793 
794 void QgsComposerItem::drawFrame( QPainter* p )
795 {
796  if ( mFrame && p )
797  {
798  p->setPen( pen() );
799  p->setBrush( Qt::NoBrush );
800  p->setRenderHint( QPainter::Antialiasing, true );
801  p->drawRect( QRectF( 0, 0, rect().width(), rect().height() ) );
802  }
803 }
804 
805 void QgsComposerItem::move( double dx, double dy )
806 {
807  QTransform t = transform();
808  QRectF newSceneRect( t.dx() + dx, t.dy() + dy, rect().width(), rect().height() );
809  setSceneRect( newSceneRect );
810 }
811 
812 void QgsComposerItem::setItemPosition( double x, double y, ItemPositionMode itemPoint )
813 {
814  double width = rect().width();
815  double height = rect().height();
816  setItemPosition( x, y, width, height, itemPoint );
817 }
818 
819 void QgsComposerItem::setItemPosition( double x, double y, double width, double height, ItemPositionMode itemPoint )
820 {
821  double upperLeftX = x;
822  double upperLeftY = y;
823 
824  //store the item position mode
825  mLastUsedPositionMode = itemPoint;
826 
827  //adjust x-coordinate if placement is not done to a left point
828  if ( itemPoint == UpperMiddle || itemPoint == Middle || itemPoint == LowerMiddle )
829  {
830  upperLeftX -= width / 2.0;
831  }
832  else if ( itemPoint == UpperRight || itemPoint == MiddleRight || itemPoint == LowerRight )
833  {
834  upperLeftX -= width;
835  }
836 
837  //adjust y-coordinate if placement is not done to an upper point
838  if ( itemPoint == MiddleLeft || itemPoint == Middle || itemPoint == MiddleRight )
839  {
840  upperLeftY -= height / 2.0;
841  }
842  else if ( itemPoint == LowerLeft || itemPoint == LowerMiddle || itemPoint == LowerRight )
843  {
844  upperLeftY -= height;
845  }
846 
847  setSceneRect( QRectF( upperLeftX, upperLeftY, width, height ) );
848 }
849 
850 void QgsComposerItem::setSceneRect( const QRectF& rectangle )
851 {
852  //setRect in item coordinates
853  double newWidth = rectangle.width();
854  double newHeight = rectangle.height();
855  double xTranslation = rectangle.x();
856  double yTranslation = rectangle.y();
857 
858  //correction if width and/or height are negative
859  if ( rectangle.width() < 0 )
860  {
861  newWidth = - rectangle.width();
862  xTranslation -= newWidth;
863  }
864 
865  if ( rectangle.height() < 0 )
866  {
867  newHeight = - rectangle.height();
868  yTranslation -= newHeight;
869  }
870 
871  QRectF newRect( 0, 0, newWidth, newHeight );
872  QGraphicsRectItem::setRect( newRect );
873 
874  //set up transformation matrix for item coordinates
875  QTransform t;
876  t.translate( xTranslation, yTranslation );
877  setTransform( t );
878 
879  emit sizeChanged();
880 }
881 
883 {
884  if ( mBackground && p )
885  {
886  p->setBrush( brush() );//this causes a problem in atlas generation
887  p->setPen( Qt::NoPen );
888  p->setRenderHint( QPainter::Antialiasing, true );
889  p->drawRect( QRectF( 0, 0, rect().width(), rect().height() ) );
890  }
891 }
892 
893 void QgsComposerItem::setBackgroundColor( const QColor& backgroundColor )
894 {
896  setBrush( QBrush( mBackgroundColor, Qt::SolidPattern ) );
897 }
898 
899 void QgsComposerItem::setBlendMode( QPainter::CompositionMode blendMode )
900 {
902  // Update the composer effect to use the new blend mode
904 }
905 
906 void QgsComposerItem::setTransparency( int transparency )
907 {
909  // Set the QGraphicItem's opacity
910  setOpacity( 1. - ( transparency / 100. ) );
911 }
912 
913 void QgsComposerItem::setEffectsEnabled( bool effectsEnabled )
914 {
915  //enable or disable the QgsComposerEffect applied to this item
917  mEffect->setEnabled( effectsEnabled );
918 }
919 
920 void QgsComposerItem::hoverMoveEvent( QGraphicsSceneHoverEvent * event )
921 {
922  if ( isSelected() )
923  {
924  setCursor( cursorForPosition( event->pos() ) );
925  }
926  else
927  {
928  setCursor( Qt::ArrowCursor );
929  }
930 }
931 
932 void QgsComposerItem::drawText( QPainter* p, double x, double y, const QString& text, const QFont& font ) const
933 {
934  QFont textFont = scaledFontPixelSize( font );
935 
936  p->save();
937  p->setFont( textFont );
938  double scaleFactor = 1.0 / FONT_WORKAROUND_SCALE;
939  p->scale( scaleFactor, scaleFactor );
940  p->drawText( QPointF( x * FONT_WORKAROUND_SCALE, y * FONT_WORKAROUND_SCALE ), text );
941  p->restore();
942 }
943 
944 void QgsComposerItem::drawText( QPainter* p, const QRectF& rect, const QString& text, const QFont& font, Qt::AlignmentFlag halignment, Qt::AlignmentFlag valignment ) const
945 {
946  QFont textFont = scaledFontPixelSize( font );
947 
948  QRectF scaledRect( rect.x() * FONT_WORKAROUND_SCALE, rect.y() * FONT_WORKAROUND_SCALE,
949  rect.width() * FONT_WORKAROUND_SCALE, rect.height() * FONT_WORKAROUND_SCALE );
950 
951  p->save();
952  p->setFont( textFont );
953  double scaleFactor = 1.0 / FONT_WORKAROUND_SCALE;
954  p->scale( scaleFactor, scaleFactor );
955  p->drawText( scaledRect, halignment | valignment | Qt::TextWordWrap, text );
956  p->restore();
957 }
958 void QgsComposerItem::drawArrowHead( QPainter* p, double x, double y, double angle, double arrowHeadWidth ) const
959 {
960  if ( !p )
961  {
962  return;
963  }
964  double angleRad = angle / 180.0 * M_PI;
965  QPointF middlePoint( x, y );
966  //rotate both arrow points
967  QPointF p1 = QPointF( -arrowHeadWidth / 2.0, arrowHeadWidth );
968  QPointF p2 = QPointF( arrowHeadWidth / 2.0, arrowHeadWidth );
969 
970  QPointF p1Rotated, p2Rotated;
971  p1Rotated.setX( p1.x() * cos( angleRad ) + p1.y() * -sin( angleRad ) );
972  p1Rotated.setY( p1.x() * sin( angleRad ) + p1.y() * cos( angleRad ) );
973  p2Rotated.setX( p2.x() * cos( angleRad ) + p2.y() * -sin( angleRad ) );
974  p2Rotated.setY( p2.x() * sin( angleRad ) + p2.y() * cos( angleRad ) );
975 
976  QPolygonF arrowHeadPoly;
977  arrowHeadPoly << middlePoint;
978  arrowHeadPoly << QPointF( middlePoint.x() + p1Rotated.x(), middlePoint.y() + p1Rotated.y() );
979  arrowHeadPoly << QPointF( middlePoint.x() + p2Rotated.x(), middlePoint.y() + p2Rotated.y() );
980 
981  p->save();
982 
983  QPen arrowPen = p->pen();
984  arrowPen.setJoinStyle( Qt::RoundJoin );
985  QBrush arrowBrush = p->brush();
986  arrowBrush.setStyle( Qt::SolidPattern );
987  p->setPen( arrowPen );
988  p->setBrush( arrowBrush );
989  arrowBrush.setStyle( Qt::SolidPattern );
990  p->drawPolygon( arrowHeadPoly );
991 
992  p->restore();
993 }
994 
995 double QgsComposerItem::textWidthMillimeters( const QFont& font, const QString& text ) const
996 {
997  QFont metricsFont = scaledFontPixelSize( font );
998  QFontMetricsF fontMetrics( metricsFont );
999  return ( fontMetrics.width( text ) / FONT_WORKAROUND_SCALE );
1000 }
1001 
1002 double QgsComposerItem::fontHeightCharacterMM( const QFont& font, const QChar& c ) const
1003 {
1004  QFont metricsFont = scaledFontPixelSize( font );
1005  QFontMetricsF fontMetrics( metricsFont );
1006  return ( fontMetrics.boundingRect( c ).height() / FONT_WORKAROUND_SCALE );
1007 }
1008 
1009 double QgsComposerItem::fontAscentMillimeters( const QFont& font ) const
1010 {
1011  QFont metricsFont = scaledFontPixelSize( font );
1012  QFontMetricsF fontMetrics( metricsFont );
1013  return ( fontMetrics.ascent() / FONT_WORKAROUND_SCALE );
1014 }
1015 
1016 double QgsComposerItem::fontDescentMillimeters( const QFont& font ) const
1017 {
1018  QFont metricsFont = scaledFontPixelSize( font );
1019  QFontMetricsF fontMetrics( metricsFont );
1020  return ( fontMetrics.descent() / FONT_WORKAROUND_SCALE );
1021 }
1022 
1023 double QgsComposerItem::pixelFontSize( double pointSize ) const
1024 {
1025  return ( pointSize * 0.3527 );
1026 }
1027 
1028 QFont QgsComposerItem::scaledFontPixelSize( const QFont& font ) const
1029 {
1030  QFont scaledFont = font;
1031  double pixelSize = pixelFontSize( font.pointSizeF() ) * FONT_WORKAROUND_SCALE + 0.5;
1032  scaledFont.setPixelSize( pixelSize );
1033  return scaledFont;
1034 }
1035 
1036 double QgsComposerItem::angle( const QPointF& p1, const QPointF& p2 ) const
1037 {
1038  double xDiff = p2.x() - p1.x();
1039  double yDiff = p2.y() - p1.y();
1040  double length = sqrt( xDiff * xDiff + yDiff * yDiff );
1041  if ( length <= 0 )
1042  {
1043  return 0;
1044  }
1045 
1046  double angle = acos(( -yDiff * length ) / ( length * length ) ) * 180 / M_PI;
1047  if ( xDiff < 0 )
1048  {
1049  return ( 360 - angle );
1050  }
1051  return angle;
1052 }
1053 
1055 {
1056  double result = -1;
1057  if ( scene() )
1058  {
1059  QList<QGraphicsView*> viewList = scene()->views();
1060  if ( viewList.size() > 0 ) //if not, probably this function was called from non-gui code
1061  {
1062  QGraphicsView* currentView = viewList.at( 0 );
1063  if ( currentView->isVisible() )
1064  {
1065  result = currentView->transform().m11();
1066  mLastValidViewScaleFactor = result;
1067  }
1068  }
1069  }
1070  return result;
1071 }
1072 
1074 {
1075  //size of symbol boxes depends on zoom level in composer view
1076  double viewScaleFactor = horizontalViewScaleFactor();
1077  double rectHandlerSize = 10.0 / viewScaleFactor;
1078 
1079  //make sure the boxes don't get too large
1080  if ( rectHandlerSize > ( rect().width() / 3 ) )
1081  {
1082  rectHandlerSize = rect().width() / 3;
1083  }
1084  if ( rectHandlerSize > ( rect().height() / 3 ) )
1085  {
1086  rectHandlerSize = rect().height() / 3;
1087  }
1088  return rectHandlerSize;
1089 }
1090 
1092 {
1093  double lockSymbolSize = 20.0 / horizontalViewScaleFactor();
1094 
1095  if ( lockSymbolSize > ( rect().width() / 3 ) )
1096  {
1097  lockSymbolSize = rect().width() / 3;
1098  }
1099  if ( lockSymbolSize > ( rect().height() / 3 ) )
1100  {
1101  lockSymbolSize = rect().height() / 3;
1102  }
1103  return lockSymbolSize;
1104 }
1105 
1106 void QgsComposerItem::updateCursor( const QPointF& itemPos )
1107 {
1108  setCursor( cursorForPosition( itemPos ) );
1109 }
1110 
1112 {
1113  if ( r > 360 )
1114  {
1115  mRotation = (( int )r ) % 360;
1116  }
1117  else
1118  {
1119  mRotation = r;
1120  }
1121  emit rotationChanged( r );
1122  update();
1123 }
1124 
1125 bool QgsComposerItem::imageSizeConsideringRotation( double& width, double& height ) const
1126 {
1127  if ( qAbs( mRotation ) <= 0.0 ) //width and height stays the same if there is no rotation
1128  {
1129  return true;
1130  }
1131 
1132  if ( qgsDoubleNear( qAbs( mRotation ), 90 ) || qgsDoubleNear( qAbs( mRotation ), 270 ) )
1133  {
1134  double tmp = width;
1135  width = height;
1136  height = tmp;
1137  return true;
1138  }
1139 
1140  double x1 = 0;
1141  double y1 = 0;
1142  double x2 = width;
1143  double y2 = 0;
1144  double x3 = width;
1145  double y3 = height;
1146  double x4 = 0;
1147  double y4 = height;
1148  double midX = width / 2.0;
1149  double midY = height / 2.0;
1150 
1151  if ( !cornerPointOnRotatedAndScaledRect( x1, y1, width, height ) )
1152  {
1153  return false;
1154  }
1155  if ( !cornerPointOnRotatedAndScaledRect( x2, y2, width, height ) )
1156  {
1157  return false;
1158  }
1159  if ( !cornerPointOnRotatedAndScaledRect( x3, y3, width, height ) )
1160  {
1161  return false;
1162  }
1163  if ( !cornerPointOnRotatedAndScaledRect( x4, y4, width, height ) )
1164  {
1165  return false;
1166  }
1167 
1168 
1169  //assume points 1 and 3 are on the rectangle boundaries. Calculate 2 and 4.
1170  double distM1 = sqrt(( x1 - midX ) * ( x1 - midX ) + ( y1 - midY ) * ( y1 - midY ) );
1171  QPointF p2 = QgsSymbolLayerV2Utils::pointOnLineWithDistance( QPointF( midX, midY ), QPointF( x2, y2 ), distM1 );
1172 
1173  if ( p2.x() < width && p2.x() > 0 && p2.y() < height && p2.y() > 0 )
1174  {
1175  width = sqrt(( p2.x() - x1 ) * ( p2.x() - x1 ) + ( p2.y() - y1 ) * ( p2.y() - y1 ) );
1176  height = sqrt(( x3 - p2.x() ) * ( x3 - p2.x() ) + ( y3 - p2.y() ) * ( y3 - p2.y() ) );
1177  return true;
1178  }
1179 
1180  //else assume that points 2 and 4 are on the rectangle boundaries. Calculate 1 and 3
1181  double distM2 = sqrt(( x2 - midX ) * ( x2 - midX ) + ( y2 - midY ) * ( y2 - midY ) );
1182  QPointF p1 = QgsSymbolLayerV2Utils::pointOnLineWithDistance( QPointF( midX, midY ), QPointF( x1, y1 ), distM2 );
1183  QPointF p3 = QgsSymbolLayerV2Utils::pointOnLineWithDistance( QPointF( midX, midY ), QPointF( x3, y3 ), distM2 );
1184  width = sqrt(( x2 - p1.x() ) * ( x2 - p1.x() ) + ( y2 - p1.y() ) * ( y2 - p1.y() ) );
1185  height = sqrt(( p3.x() - x2 ) * ( p3.x() - x2 ) + ( p3.y() - y2 ) * ( p3.y() - y2 ) );
1186  return true;
1187 }
1188 
1189 bool QgsComposerItem::cornerPointOnRotatedAndScaledRect( double& x, double& y, double width, double height ) const
1190 {
1191  //first rotate point clockwise
1192  double rotToRad = mRotation * M_PI / 180.0;
1193  QPointF midpoint( width / 2.0, height / 2.0 );
1194  double xVector = x - midpoint.x();
1195  double yVector = y - midpoint.y();
1196  //double xRotated = cos(rotToRad) * xVector + sin(rotToRad) * yVector;
1197  //double yRotated = -sin(rotToRad) * xVector + cos(rotToRad) * yVector;
1198  double xRotated = cos( rotToRad ) * xVector - sin( rotToRad ) * yVector;
1199  double yRotated = sin( rotToRad ) * xVector + cos( rotToRad ) * yVector;
1200 
1201  //create line from midpoint to rotated point
1202  QLineF line( midpoint.x(), midpoint.y(), midpoint.x() + xRotated, midpoint.y() + yRotated );
1203 
1204  //intersect with all four borders and return result
1205  QList<QLineF> borders;
1206  borders << QLineF( 0, 0, width, 0 );
1207  borders << QLineF( width, 0, width, height );
1208  borders << QLineF( width, height, 0, height );
1209  borders << QLineF( 0, height, 0, 0 );
1210 
1211  QList<QLineF>::const_iterator it = borders.constBegin();
1212  QPointF intersectionPoint;
1213 
1214  for ( ; it != borders.constEnd(); ++it )
1215  {
1216  if ( line.intersect( *it, &intersectionPoint ) == QLineF::BoundedIntersection )
1217  {
1218  x = intersectionPoint.x();
1219  y = intersectionPoint.y();
1220  return true;
1221  }
1222  }
1223  return false;
1224 }
1225 
1226 void QgsComposerItem::sizeChangedByRotation( double& width, double& height )
1227 {
1228  if ( mRotation == 0.0 )
1229  {
1230  return;
1231  }
1232 
1233  //vector to p1
1234  double x1 = -width / 2.0;
1235  double y1 = -height / 2.0;
1236  rotate( mRotation, x1, y1 );
1237  //vector to p2
1238  double x2 = width / 2.0;
1239  double y2 = -height / 2.0;
1240  rotate( mRotation, x2, y2 );
1241  //vector to p3
1242  double x3 = width / 2.0;
1243  double y3 = height / 2.0;
1244  rotate( mRotation, x3, y3 );
1245  //vector to p4
1246  double x4 = -width / 2.0;
1247  double y4 = height / 2.0;
1248  rotate( mRotation, x4, y4 );
1249 
1250  //double midpoint
1251  QPointF midpoint( width / 2.0, height / 2.0 );
1252 
1253  QPolygonF rotatedRectPoly;
1254  rotatedRectPoly << QPointF( midpoint.x() + x1, midpoint.y() + y1 );
1255  rotatedRectPoly << QPointF( midpoint.x() + x2, midpoint.y() + y2 );
1256  rotatedRectPoly << QPointF( midpoint.x() + x3, midpoint.y() + y3 );
1257  rotatedRectPoly << QPointF( midpoint.x() + x4, midpoint.y() + y4 );
1258  QRectF boundingRect = rotatedRectPoly.boundingRect();
1259  width = boundingRect.width();
1260  height = boundingRect.height();
1261 }
1262 
1263 void QgsComposerItem::rotate( double angle, double& x, double& y ) const
1264 {
1265  double rotToRad = angle * M_PI / 180.0;
1266  double xRot, yRot;
1267  xRot = x * cos( rotToRad ) - y * sin( rotToRad );
1268  yRot = x * sin( rotToRad ) + y * cos( rotToRad );
1269  x = xRot;
1270  y = yRot;
1271 }
1272 
1274 {
1275  if ( !mHAlignSnapItem )
1276  {
1277  mHAlignSnapItem = new QGraphicsLineItem( 0 );
1278  mHAlignSnapItem->setPen( QPen( QColor( Qt::red ) ) );
1279  scene()->addItem( mHAlignSnapItem );
1280  mHAlignSnapItem->setZValue( 90 );
1281  }
1282  return mHAlignSnapItem;
1283 }
1284 
1286 {
1287  if ( !mVAlignSnapItem )
1288  {
1289  mVAlignSnapItem = new QGraphicsLineItem( 0 );
1290  mVAlignSnapItem->setPen( QPen( QColor( Qt::red ) ) );
1291  scene()->addItem( mVAlignSnapItem );
1292  mVAlignSnapItem->setZValue( 90 );
1293  }
1294  return mVAlignSnapItem;
1295 }
1296 
1298 {
1299  if ( mHAlignSnapItem )
1300  {
1301  scene()->removeItem( mHAlignSnapItem );
1302  delete mHAlignSnapItem;
1303  mHAlignSnapItem = 0;
1304  }
1305 }
1306 
1308 {
1309  if ( mVAlignSnapItem )
1310  {
1311  scene()->removeItem( mVAlignSnapItem );
1312  delete mVAlignSnapItem;
1313  mVAlignSnapItem = 0;
1314  }
1315 }
1316 
1318 {
1321 }
1322 
1324 {
1325  update();
1326 }
1327 
1328 void QgsComposerItem::setId( const QString& id )
1329 {
1330  setToolTip( id );
1331  mId = id;
1332 }