QGIS API Documentation  2.8.2-Wien
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgscomposermousehandles.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscomposermousehandles.cpp
3  -------------------
4  begin : September 2013
5  copyright : (C) 2013 by Nyall Dawson, Radim Blazek
6  email : nyall.dawson@gmail.com
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 <QGraphicsView>
18 #include <QGraphicsSceneHoverEvent>
19 #include <QPainter>
20 #include <QWidget>
21 
22 #include <limits>
23 
25 #include "qgscomposeritem.h"
26 #include "qgscomposition.h"
27 #include "qgscomposerutils.h"
28 #include "qgspaperitem.h"
29 #include "qgis.h"
30 #include "qgslogger.h"
31 #include "qgsproject.h"
32 
34  : QObject( 0 )
35  , QGraphicsRectItem( 0 )
36  , mComposition( composition )
37  , mGraphicsView( 0 )
38  , mCurrentMouseMoveAction( NoAction )
39  , mBeginHandleWidth( 0 )
40  , mBeginHandleHeight( 0 )
41  , mResizeMoveX( 0 )
42  , mResizeMoveY( 0 )
43  , mIsDragging( false )
44  , mIsResizing( false )
45  , mHAlignSnapItem( 0 )
46  , mVAlignSnapItem( 0 )
47 {
48  //listen for selection changes, and update handles accordingly
49  QObject::connect( mComposition, SIGNAL( selectionChanged() ), this, SLOT( selectionChanged() ) );
50 
51  //accept hover events, required for changing cursor to resize cursors
52  setAcceptHoverEvents( true );
53 }
54 
56 {
57 
58 }
59 
60 QGraphicsView* QgsComposerMouseHandles::graphicsView()
61 {
62  //have we already found the current view?
63  if ( mGraphicsView )
64  {
65  return mGraphicsView;
66  }
67 
68  //otherwise, try and find current view attached to composition
69  if ( scene() )
70  {
71  QList<QGraphicsView*> viewList = scene()->views();
72  if ( viewList.size() > 0 )
73  {
74  mGraphicsView = viewList.at( 0 );
75  return mGraphicsView;
76  }
77  }
78 
79  //no view attached to composition
80  return 0;
81 }
82 
83 void QgsComposerMouseHandles::paint( QPainter* painter, const QStyleOptionGraphicsItem* itemStyle, QWidget* pWidget )
84 {
85  Q_UNUSED( itemStyle );
86  Q_UNUSED( pWidget );
87 
88  if ( mComposition->plotStyle() != QgsComposition::Preview )
89  {
90  //don't draw selection handles in composition outputs
91  return;
92  }
93 
94  if ( mComposition->boundingBoxesVisible() )
95  {
96  //draw resize handles around bounds of entire selection
97  double rectHandlerSize = rectHandlerBorderTolerance();
98  drawHandles( painter, rectHandlerSize );
99  }
100 
101  if ( mIsResizing || mIsDragging || mComposition->boundingBoxesVisible() )
102  {
103  //draw dotted boxes around selected items
104  drawSelectedItemBounds( painter );
105  }
106 }
107 
108 void QgsComposerMouseHandles::drawHandles( QPainter* painter, double rectHandlerSize )
109 {
110  //blue, zero width cosmetic pen for outline
111  QPen handlePen = QPen( QColor( 55, 140, 195, 255 ) );
112  handlePen.setWidth( 0 );
113  painter->setPen( handlePen );
114 
115  //draw box around entire selection bounds
116  painter->setBrush( Qt::NoBrush );
117  painter->drawRect( QRectF( 0, 0, rect().width(), rect().height() ) );
118 
119  //draw resize handles, using a filled white box
120  painter->setBrush( QColor( 255, 255, 255, 255 ) );
121  //top left
122  painter->drawRect( QRectF( 0, 0, rectHandlerSize, rectHandlerSize ) );
123  //mid top
124  painter->drawRect( QRectF(( rect().width() - rectHandlerSize ) / 2, 0, rectHandlerSize, rectHandlerSize ) );
125  //top right
126  painter->drawRect( QRectF( rect().width() - rectHandlerSize, 0, rectHandlerSize, rectHandlerSize ) );
127  //mid left
128  painter->drawRect( QRectF( 0, ( rect().height() - rectHandlerSize ) / 2, rectHandlerSize, rectHandlerSize ) );
129  //mid right
130  painter->drawRect( QRectF( rect().width() - rectHandlerSize, ( rect().height() - rectHandlerSize ) / 2, rectHandlerSize, rectHandlerSize ) );
131  //bottom left
132  painter->drawRect( QRectF( 0, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) );
133  //mid bottom
134  painter->drawRect( QRectF(( rect().width() - rectHandlerSize ) / 2, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) );
135  //bottom right
136  painter->drawRect( QRectF( rect().width() - rectHandlerSize, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) );
137 }
138 
139 void QgsComposerMouseHandles::drawSelectedItemBounds( QPainter* painter )
140 {
141  //draw dotted border around selected items to give visual feedback which items are selected
142  QList<QgsComposerItem*> selectedItems = mComposition->selectedComposerItems( false );
143  if ( selectedItems.size() == 0 )
144  {
145  return;
146  }
147 
148  //use difference mode so that they are visible regardless of item colors
149  painter->save();
150  painter->setCompositionMode( QPainter::CompositionMode_Difference );
151 
152  // use a grey dashed pen - in difference mode this should always be visible
153  QPen selectedItemPen = QPen( QColor( 144, 144, 144, 255 ) );
154  selectedItemPen.setStyle( Qt::DashLine );
155  selectedItemPen.setWidth( 0 );
156  painter->setPen( selectedItemPen );
157  painter->setBrush( Qt::NoBrush );
158 
159  QList<QgsComposerItem*>::iterator itemIter = selectedItems.begin();
160  for ( ; itemIter != selectedItems.end(); ++itemIter )
161  {
162  //get bounds of selected item
163  QPolygonF itemBounds;
164  if ( mIsDragging && !( *itemIter )->positionLock() )
165  {
166  //if currently dragging, draw selected item bounds relative to current mouse position
167  //first, get bounds of current item in scene coordinates
168  QPolygonF itemSceneBounds = ( *itemIter )->mapToScene(( *itemIter )->rectWithFrame() );
169  //now, translate it by the current movement amount
170  //IMPORTANT - this is done in scene coordinates, since we don't want any rotation/non-translation transforms to affect the movement
171  itemSceneBounds.translate( transform().dx(), transform().dy() );
172  //finally, remap it to the mouse handle item's coordinate system so it's ready for drawing
173  itemBounds = mapFromScene( itemSceneBounds );
174  }
175  else if ( mIsResizing && !( *itemIter )->positionLock() )
176  {
177  //if currently resizing, calculate relative resize of this item
178  if ( selectedItems.size() > 1 )
179  {
180  //get item bounds in mouse handle item's coordinate system
181  QRectF itemRect = mapRectFromItem(( *itemIter ), ( *itemIter )->rectWithFrame() );
182  //now, resize it relative to the current resized dimensions of the mouse handles
183  QgsComposerUtils::relativeResizeRect( itemRect, QRectF( -mResizeMoveX, -mResizeMoveY, mBeginHandleWidth, mBeginHandleHeight ), mResizeRect );
184  itemBounds = QPolygonF( itemRect );
185  }
186  else
187  {
188  //single item selected
189  itemBounds = rect();
190  }
191  }
192  else
193  {
194  //not resizing or moving, so just map from scene bounds
195  itemBounds = mapRectFromItem(( *itemIter ), ( *itemIter )->rectWithFrame() );
196  }
197  painter->drawPolygon( itemBounds );
198  }
199  painter->restore();
200 }
201 
203 {
204  //listen out for selected items' size and rotation changed signals
205  QList<QGraphicsItem *> itemList = composition()->items();
206  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
207  for ( ; itemIt != itemList.end(); ++itemIt )
208  {
209  QgsComposerItem* item = dynamic_cast<QgsComposerItem *>( *itemIt );
210  if ( item )
211  {
212  if ( item->selected() )
213  {
214  QObject::connect( item, SIGNAL( sizeChanged() ), this, SLOT( selectedItemSizeChanged() ) );
215  QObject::connect( item, SIGNAL( itemRotationChanged( double ) ), this, SLOT( selectedItemRotationChanged() ) );
216  QObject::connect( item, SIGNAL( frameChanged() ), this, SLOT( selectedItemSizeChanged() ) );
217  QObject::connect( item, SIGNAL( lockChanged() ), this, SLOT( selectedItemSizeChanged() ) );
218  }
219  else
220  {
221  QObject::disconnect( item, SIGNAL( sizeChanged() ), this, 0 );
222  QObject::disconnect( item, SIGNAL( itemRotationChanged( double ) ), this, 0 );
223  QObject::disconnect( item, SIGNAL( frameChanged() ), this, 0 );
224  QObject::disconnect( item, SIGNAL( lockChanged() ), this, 0 );
225  }
226  }
227  }
228 
229  resetStatusBar();
230  updateHandles();
231 }
232 
234 {
235  if ( !mIsDragging && !mIsResizing )
236  {
237  //only required for non-mouse initiated size changes
238  updateHandles();
239  }
240 }
241 
243 {
244  if ( !mIsDragging && !mIsResizing )
245  {
246  //only required for non-mouse initiated rotation changes
247  updateHandles();
248  }
249 }
250 
251 void QgsComposerMouseHandles::updateHandles()
252 {
253  //recalculate size and position of handle item
254 
255  //first check to see if any items are selected
256  QList<QgsComposerItem*> selectedItems = mComposition->selectedComposerItems( false );
257  if ( selectedItems.size() > 0 )
258  {
259  //one or more items are selected, get bounds of all selected items
260 
261  //update rotation of handle object
262  double rotation;
263  if ( selectionRotation( rotation ) )
264  {
265  //all items share a common rotation value, so we rotate the mouse handles to match
266  setRotation( rotation );
267  }
268  else
269  {
270  //items have varying rotation values - we can't rotate the mouse handles to match
271  setRotation( 0 );
272  }
273 
274  //get bounds of all selected items
275  QRectF newHandleBounds = selectionBounds();
276 
277  //update size and position of handle object
278  setRect( 0, 0, newHandleBounds.width(), newHandleBounds.height() );
279  setPos( mapToScene( newHandleBounds.topLeft() ) );
280 
281  show();
282  }
283  else
284  {
285  //no items selected, hide handles
286  hide();
287  }
288  //force redraw
289  update();
290 }
291 
292 QRectF QgsComposerMouseHandles::selectionBounds() const
293 {
294  //calculate bounds of all currently selected items in mouse handle coordinate system
295  QList<QgsComposerItem*> selectedItems = mComposition->selectedComposerItems( false );
296  QList<QgsComposerItem*>::iterator itemIter = selectedItems.begin();
297 
298  //start with handle bounds of first selected item
299  QRectF bounds = mapFromItem(( *itemIter ), ( *itemIter )->rectWithFrame() ).boundingRect();
300 
301  //iterate through remaining items, expanding the bounds as required
302  for ( ++itemIter; itemIter != selectedItems.end(); ++itemIter )
303  {
304  bounds = bounds.united( mapFromItem(( *itemIter ), ( *itemIter )->rectWithFrame() ).boundingRect() );
305  }
306 
307  return bounds;
308 }
309 
310 bool QgsComposerMouseHandles::selectionRotation( double & rotation ) const
311 {
312  //check if all selected items have same rotation
313  QList<QgsComposerItem*> selectedItems = mComposition->selectedComposerItems( false );
314  QList<QgsComposerItem*>::iterator itemIter = selectedItems.begin();
315 
316  //start with rotation of first selected item
317  double firstItemRotation = ( *itemIter )->itemRotation();
318 
319  //iterate through remaining items, checking if they have same rotation
320  for ( ++itemIter; itemIter != selectedItems.end(); ++itemIter )
321  {
322  if (( *itemIter )->itemRotation() != firstItemRotation )
323  {
324  //item has a different rotation, so return false
325  return false;
326  }
327  }
328 
329  //all items have the same rotation, so set the rotation variable and return true
330  rotation = firstItemRotation;
331  return true;
332 }
333 
334 double QgsComposerMouseHandles::rectHandlerBorderTolerance()
335 {
336  //calculate size for resize handles
337  //get view scale factor
338  double viewScaleFactor = graphicsView()->transform().m11();
339 
340  //size of handle boxes depends on zoom level in composer view
341  double rectHandlerSize = 10.0 / viewScaleFactor;
342 
343  //make sure the boxes don't get too large
344  if ( rectHandlerSize > ( rect().width() / 3 ) )
345  {
346  rectHandlerSize = rect().width() / 3;
347  }
348  if ( rectHandlerSize > ( rect().height() / 3 ) )
349  {
350  rectHandlerSize = rect().height() / 3;
351  }
352  return rectHandlerSize;
353 }
354 
355 Qt::CursorShape QgsComposerMouseHandles::cursorForPosition( const QPointF& itemCoordPos )
356 {
357  QgsComposerMouseHandles::MouseAction mouseAction = mouseActionForPosition( itemCoordPos );
358  switch ( mouseAction )
359  {
360  case NoAction:
361  return Qt::ForbiddenCursor;
362  case MoveItem:
363  return Qt::SizeAllCursor;
364  case ResizeUp:
365  case ResizeDown:
366  //account for rotation
367  if (( rotation() <= 22.5 || rotation() >= 337.5 ) || ( rotation() >= 157.5 && rotation() <= 202.5 ) )
368  {
369  return Qt::SizeVerCursor;
370  }
371  else if (( rotation() >= 22.5 && rotation() <= 67.5 ) || ( rotation() >= 202.5 && rotation() <= 247.5 ) )
372  {
373  return Qt::SizeBDiagCursor;
374  }
375  else if (( rotation() >= 67.5 && rotation() <= 112.5 ) || ( rotation() >= 247.5 && rotation() <= 292.5 ) )
376  {
377  return Qt::SizeHorCursor;
378  }
379  else
380  {
381  return Qt::SizeFDiagCursor;
382  }
383  case ResizeLeft:
384  case ResizeRight:
385  //account for rotation
386  if (( rotation() <= 22.5 || rotation() >= 337.5 ) || ( rotation() >= 157.5 && rotation() <= 202.5 ) )
387  {
388  return Qt::SizeHorCursor;
389  }
390  else if (( rotation() >= 22.5 && rotation() <= 67.5 ) || ( rotation() >= 202.5 && rotation() <= 247.5 ) )
391  {
392  return Qt::SizeFDiagCursor;
393  }
394  else if (( rotation() >= 67.5 && rotation() <= 112.5 ) || ( rotation() >= 247.5 && rotation() <= 292.5 ) )
395  {
396  return Qt::SizeVerCursor;
397  }
398  else
399  {
400  return Qt::SizeBDiagCursor;
401  }
402 
403  case ResizeLeftUp:
404  case ResizeRightDown:
405  //account for rotation
406  if (( rotation() <= 22.5 || rotation() >= 337.5 ) || ( rotation() >= 157.5 && rotation() <= 202.5 ) )
407  {
408  return Qt::SizeFDiagCursor;
409  }
410  else if (( rotation() >= 22.5 && rotation() <= 67.5 ) || ( rotation() >= 202.5 && rotation() <= 247.5 ) )
411  {
412  return Qt::SizeVerCursor;
413  }
414  else if (( rotation() >= 67.5 && rotation() <= 112.5 ) || ( rotation() >= 247.5 && rotation() <= 292.5 ) )
415  {
416  return Qt::SizeBDiagCursor;
417  }
418  else
419  {
420  return Qt::SizeHorCursor;
421  }
422  case ResizeRightUp:
423  case ResizeLeftDown:
424  //account for rotation
425  if (( rotation() <= 22.5 || rotation() >= 337.5 ) || ( rotation() >= 157.5 && rotation() <= 202.5 ) )
426  {
427  return Qt::SizeBDiagCursor;
428  }
429  else if (( rotation() >= 22.5 && rotation() <= 67.5 ) || ( rotation() >= 202.5 && rotation() <= 247.5 ) )
430  {
431  return Qt::SizeHorCursor;
432  }
433  else if (( rotation() >= 67.5 && rotation() <= 112.5 ) || ( rotation() >= 247.5 && rotation() <= 292.5 ) )
434  {
435  return Qt::SizeFDiagCursor;
436  }
437  else
438  {
439  return Qt::SizeVerCursor;
440  }
441  case SelectItem:
442  default:
443  return Qt::ArrowCursor;
444  }
445 }
446 
447 QgsComposerMouseHandles::MouseAction QgsComposerMouseHandles::mouseActionForPosition( const QPointF& itemCoordPos )
448 {
449  bool nearLeftBorder = false;
450  bool nearRightBorder = false;
451  bool nearLowerBorder = false;
452  bool nearUpperBorder = false;
453 
454  bool withinWidth = false;
455  bool withinHeight = false;
456  if ( itemCoordPos.x() >= 0 && itemCoordPos.x() <= rect().width() )
457  {
458  withinWidth = true;
459  }
460  if ( itemCoordPos.y() >= 0 && itemCoordPos.y() <= rect().height() )
461  {
462  withinHeight = true;
463  }
464 
465  double borderTolerance = rectHandlerBorderTolerance();
466 
467  if ( itemCoordPos.x() >= 0 && itemCoordPos.x() < borderTolerance )
468  {
469  nearLeftBorder = true;
470  }
471  if ( itemCoordPos.y() >= 0 && itemCoordPos.y() < borderTolerance )
472  {
473  nearUpperBorder = true;
474  }
475  if ( itemCoordPos.x() <= rect().width() && itemCoordPos.x() > ( rect().width() - borderTolerance ) )
476  {
477  nearRightBorder = true;
478  }
479  if ( itemCoordPos.y() <= rect().height() && itemCoordPos.y() > ( rect().height() - borderTolerance ) )
480  {
481  nearLowerBorder = true;
482  }
483 
484  if ( nearLeftBorder && nearUpperBorder )
485  {
487  }
488  else if ( nearLeftBorder && nearLowerBorder )
489  {
491  }
492  else if ( nearRightBorder && nearUpperBorder )
493  {
495  }
496  else if ( nearRightBorder && nearLowerBorder )
497  {
499  }
500  else if ( nearLeftBorder && withinHeight )
501  {
503  }
504  else if ( nearRightBorder && withinHeight )
505  {
507  }
508  else if ( nearUpperBorder && withinWidth )
509  {
511  }
512  else if ( nearLowerBorder && withinWidth )
513  {
515  }
516 
517  //find out if cursor position is over a selected item
518  QPointF scenePoint = mapToScene( itemCoordPos );
519  QList<QGraphicsItem *> itemsAtCursorPos = mComposition->items( scenePoint );
520  if ( itemsAtCursorPos.size() == 0 )
521  {
522  //no items at cursor position
524  }
525  QList<QGraphicsItem*>::iterator itemIter = itemsAtCursorPos.begin();
526  for ( ; itemIter != itemsAtCursorPos.end(); ++itemIter )
527  {
528  QgsComposerItem* item = dynamic_cast<QgsComposerItem *>(( *itemIter ) );
529  if ( item && item->selected() )
530  {
531  //cursor is over a selected composer item
533  }
534  }
535 
536  //default
538 }
539 
541 {
542  // convert sceneCoordPos to item coordinates
543  QPointF itemPos = mapFromScene( sceneCoordPos );
544  return mouseActionForPosition( itemPos );
545 }
546 
547 void QgsComposerMouseHandles::hoverMoveEvent( QGraphicsSceneHoverEvent * event )
548 {
549  setViewportCursor( cursorForPosition( event->pos() ) );
550 }
551 
552 void QgsComposerMouseHandles::hoverLeaveEvent( QGraphicsSceneHoverEvent * event )
553 {
554  Q_UNUSED( event );
555  setViewportCursor( Qt::ArrowCursor );
556 }
557 
558 void QgsComposerMouseHandles::setViewportCursor( Qt::CursorShape cursor )
559 {
560  //workaround qt bug #3732 by setting cursor for QGraphicsView viewport,
561  //rather then setting it directly here
562 
563  if ( !mComposition->preventCursorChange() )
564  {
565  graphicsView()->viewport()->setCursor( cursor );
566  }
567 }
568 
569 void QgsComposerMouseHandles::mouseMoveEvent( QGraphicsSceneMouseEvent* event )
570 {
571  if ( mIsDragging )
572  {
573  //currently dragging a selection
574  //if shift depressed, constrain movement to horizontal/vertical
575  //if control depressed, ignore snapping
576  dragMouseMove( event->lastScenePos(), event->modifiers() & Qt::ShiftModifier, event->modifiers() & Qt::ControlModifier );
577  }
578  else if ( mIsResizing )
579  {
580  //currently resizing a selection
581  //lock aspect ratio if shift depressed
582  //resize from center if alt depressed
583  resizeMouseMove( event->lastScenePos(), event->modifiers() & Qt::ShiftModifier, event->modifiers() & Qt::AltModifier );
584  }
585 
586  mLastMouseEventPos = event->lastScenePos();
587 }
588 
589 void QgsComposerMouseHandles::mouseReleaseEvent( QGraphicsSceneMouseEvent* event )
590 {
591  QPointF mouseMoveStopPoint = event->lastScenePos();
592  double diffX = mouseMoveStopPoint.x() - mMouseMoveStartPos.x();
593  double diffY = mouseMoveStopPoint.y() - mMouseMoveStartPos.y();
594 
595  //it was only a click
596  if ( qAbs( diffX ) < std::numeric_limits<double>::min() && qAbs( diffY ) < std::numeric_limits<double>::min() )
597  {
598  mIsDragging = false;
599  mIsResizing = false;
600  update();
601  return;
602  }
603 
604  if ( mCurrentMouseMoveAction == QgsComposerMouseHandles::MoveItem )
605  {
606  //move selected items
607  QUndoCommand* parentCommand = new QUndoCommand( tr( "Change item position" ) );
608 
609  QPointF mEndHandleMovePos = scenePos();
610 
611  //move all selected items
612  QList<QgsComposerItem*> selectedItems = mComposition->selectedComposerItems( false );
613  QList<QgsComposerItem*>::iterator itemIter = selectedItems.begin();
614  for ( ; itemIter != selectedItems.end(); ++itemIter )
615  {
616  if (( *itemIter )->positionLock() || (( *itemIter )->flags() & QGraphicsItem::ItemIsSelectable ) == 0 )
617  {
618  //don't move locked items
619  continue;
620  }
621  QgsComposerItemCommand* subcommand = new QgsComposerItemCommand( *itemIter, "", parentCommand );
622  subcommand->savePreviousState();
623  ( *itemIter )->move( mEndHandleMovePos.x() - mBeginHandlePos.x(), mEndHandleMovePos.y() - mBeginHandlePos.y() );
624  subcommand->saveAfterState();
625  }
626  mComposition->undoStack()->push( parentCommand );
627  QgsProject::instance()->dirty( true );
628  }
629  else if ( mCurrentMouseMoveAction != QgsComposerMouseHandles::NoAction )
630  {
631  //resize selected items
632  QUndoCommand* parentCommand = new QUndoCommand( tr( "Change item size" ) );
633 
634  //resize all selected items
635  QList<QgsComposerItem*> selectedItems = mComposition->selectedComposerItems( false );
636  QList<QgsComposerItem*>::iterator itemIter = selectedItems.begin();
637  for ( ; itemIter != selectedItems.end(); ++itemIter )
638  {
639  if (( *itemIter )->positionLock() || (( *itemIter )->flags() & QGraphicsItem::ItemIsSelectable ) == 0 )
640  {
641  //don't resize locked items or unselectable items (eg, items which make up an item group)
642  continue;
643  }
644  QgsComposerItemCommand* subcommand = new QgsComposerItemCommand( *itemIter, "", parentCommand );
645  subcommand->savePreviousState();
646 
647  QRectF itemRect;
648  if ( selectedItems.size() == 1 )
649  {
650  //only a single item is selected, so set its size to the final resized mouse handle size
651  itemRect = mResizeRect;
652  }
653  else
654  {
655  //multiple items selected, so each needs to be scaled relatively to the final size of the mouse handles
656  itemRect = mapRectFromItem(( *itemIter ), ( *itemIter )->rectWithFrame() );
657  QgsComposerUtils::relativeResizeRect( itemRect, QRectF( -mResizeMoveX, -mResizeMoveY, mBeginHandleWidth, mBeginHandleHeight ), mResizeRect );
658  }
659 
660  itemRect = itemRect.normalized();
661  QPointF newPos = mapToScene( itemRect.topLeft() );
662  ( *itemIter )->setItemPosition( newPos.x(), newPos.y(), itemRect.width(), itemRect.height(), QgsComposerItem::UpperLeft, true );
663 
664  subcommand->saveAfterState();
665  }
666  mComposition->undoStack()->push( parentCommand );
667  QgsProject::instance()->dirty( true );
668  }
669 
670  deleteAlignItems();
671 
672  if ( mIsDragging )
673  {
674  mIsDragging = false;
675  }
676  if ( mIsResizing )
677  {
678  mIsResizing = false;
679  }
680 
681  //reset default action
682  mCurrentMouseMoveAction = QgsComposerMouseHandles::MoveItem;
683  setViewportCursor( Qt::ArrowCursor );
684  //redraw handles
685  resetTransform();
686  updateHandles();
687  //reset status bar message
688  resetStatusBar();
689 }
690 
691 void QgsComposerMouseHandles::resetStatusBar()
692 {
693  QList<QgsComposerItem*> selectedItems = mComposition->selectedComposerItems( false );
694  int selectedCount = selectedItems.size();
695  if ( selectedCount > 1 )
696  {
697  //set status bar message to count of selected items
698  mComposition->setStatusMessage( QString( tr( "%1 items selected" ) ).arg( selectedCount ) );
699  }
700  else if ( selectedCount == 1 )
701  {
702  //set status bar message to count of selected items
703  mComposition->setStatusMessage( tr( "1 item selected" ) );
704  }
705  else
706  {
707  //clear status bar message
708  mComposition->setStatusMessage( QString( "" ) );
709  }
710 }
711 
712 void QgsComposerMouseHandles::mousePressEvent( QGraphicsSceneMouseEvent* event )
713 {
714  //save current cursor position
715  mMouseMoveStartPos = event->lastScenePos();
716  mLastMouseEventPos = event->lastScenePos();
717  //save current item geometry
718  mBeginMouseEventPos = event->lastScenePos();
719  mBeginHandlePos = scenePos();
720  mBeginHandleWidth = rect().width();
721  mBeginHandleHeight = rect().height();
722  //type of mouse move action
723  mCurrentMouseMoveAction = mouseActionForPosition( event->pos() );
724 
725  deleteAlignItems();
726 
727  if ( mCurrentMouseMoveAction == QgsComposerMouseHandles::MoveItem )
728  {
729  //moving items
730  mIsDragging = true;
731  }
732  else if ( mCurrentMouseMoveAction != QgsComposerMouseHandles::SelectItem &&
733  mCurrentMouseMoveAction != QgsComposerMouseHandles::NoAction )
734  {
735  //resizing items
736  mIsResizing = true;
737  mResizeRect = QRectF( 0, 0, mBeginHandleWidth, mBeginHandleHeight );
738  mResizeMoveX = 0;
739  mResizeMoveY = 0;
740  mCursorOffset = calcCursorEdgeOffset( mMouseMoveStartPos );
741 
742  }
743 
744 }
745 
746 void QgsComposerMouseHandles::mouseDoubleClickEvent( QGraphicsSceneMouseEvent *event )
747 {
748  Q_UNUSED( event );
749 }
750 
751 QSizeF QgsComposerMouseHandles::calcCursorEdgeOffset( const QPointF &cursorPos )
752 {
753  //find offset between cursor position and actual edge of item
754  QPointF sceneMousePos = mapFromScene( cursorPos );
755 
756  switch ( mCurrentMouseMoveAction )
757  {
758  //vertical resize
760  return QSizeF( 0, sceneMousePos.y() );
761 
763  return QSizeF( 0, sceneMousePos.y() - rect().height() );
764 
765  //horizontal resize
767  return QSizeF( sceneMousePos.x(), 0 );
768 
770  return QSizeF( sceneMousePos.x() - rect().width(), 0 );
771 
772  //diagonal resize
774  return QSizeF( sceneMousePos.x(), sceneMousePos.y() );
775 
777  return QSizeF( sceneMousePos.x() - rect().width(), sceneMousePos.y() - rect().height() );
778 
780  return QSizeF( sceneMousePos.x() - rect().width(), sceneMousePos.y() );
781 
783  return QSizeF( sceneMousePos.x(), sceneMousePos.y() - rect().height() );
784 
785  default:
786  return QSizeF( 0, 0 );
787  }
788 
789 }
790 
791 void QgsComposerMouseHandles::dragMouseMove( const QPointF& currentPosition, bool lockMovement, bool preventSnap )
792 {
793  if ( !mComposition )
794  {
795  return;
796  }
797 
798  //calculate total amount of mouse movement since drag began
799  double moveX = currentPosition.x() - mBeginMouseEventPos.x();
800  double moveY = currentPosition.y() - mBeginMouseEventPos.y();
801 
802  //find target position before snapping (in scene coordinates)
803  QPointF upperLeftPoint( mBeginHandlePos.x() + moveX, mBeginHandlePos.y() + moveY );
804 
805  QPointF snappedLeftPoint;
806  //no snapping for rotated items for now
807  if ( !preventSnap && rotation() == 0 )
808  {
809  //snap to grid and guides
810  snappedLeftPoint = snapPoint( upperLeftPoint, QgsComposerMouseHandles::Item );
811  }
812  else
813  {
814  //no snapping
815  snappedLeftPoint = upperLeftPoint;
816  deleteAlignItems();
817  }
818 
819  //calculate total shift for item from beginning of drag operation to current position
820  double moveRectX = snappedLeftPoint.x() - mBeginHandlePos.x();
821  double moveRectY = snappedLeftPoint.y() - mBeginHandlePos.y();
822 
823  if ( lockMovement )
824  {
825  //constrained (shift) moving should lock to horizontal/vertical movement
826  //reset the smaller of the x/y movements
827  if ( qAbs( moveRectX ) <= qAbs( moveRectY ) )
828  {
829  moveRectX = 0;
830  }
831  else
832  {
833  moveRectY = 0;
834  }
835  }
836 
837  //shift handle item to new position
838  QTransform moveTransform;
839  moveTransform.translate( moveRectX, moveRectY );
840  setTransform( moveTransform );
841  //show current displacement of selection in status bar
842  mComposition->setStatusMessage( QString( tr( "dx: %1 mm dy: %2 mm" ) ).arg( moveRectX ).arg( moveRectY ) );
843 }
844 
845 void QgsComposerMouseHandles::resizeMouseMove( const QPointF& currentPosition, bool lockRatio, bool fromCenter )
846 {
847 
848  if ( !mComposition )
849  {
850  return;
851  }
852 
853  double mx = 0.0, my = 0.0, rx = 0.0, ry = 0.0;
854 
855  QPointF beginMousePos;
856  QPointF finalPosition;
857  if ( rotation() == 0 )
858  {
859  //snapping only occurs if handles are not rotated for now
860 
861  //subtract cursor edge offset from begin mouse event and current cursor position, so that snapping occurs to edge of mouse handles
862  //rather then cursor position
863  beginMousePos = mapFromScene( QPointF( mBeginMouseEventPos.x() - mCursorOffset.width(), mBeginMouseEventPos.y() - mCursorOffset.height() ) );
864  QPointF snappedPosition = snapPoint( QPointF( currentPosition.x() - mCursorOffset.width(), currentPosition.y() - mCursorOffset.height() ), QgsComposerMouseHandles::Point );
865  finalPosition = mapFromScene( snappedPosition );
866  }
867  else
868  {
869  //no snapping for rotated items for now
870  beginMousePos = mapFromScene( mBeginMouseEventPos );
871  finalPosition = mapFromScene( currentPosition );
872  }
873 
874  double diffX = finalPosition.x() - beginMousePos.x();
875  double diffY = finalPosition.y() - beginMousePos.y();
876 
877  double ratio = 0;
878  if ( lockRatio && mBeginHandleHeight != 0 )
879  {
880  ratio = mBeginHandleWidth / mBeginHandleHeight;
881  }
882 
883  switch ( mCurrentMouseMoveAction )
884  {
885  //vertical resize
887  {
888  if ( ratio )
889  {
890  diffX = (( mBeginHandleHeight - diffY ) * ratio ) - mBeginHandleWidth;
891  mx = -diffX / 2; my = diffY; rx = diffX; ry = -diffY;
892  }
893  else
894  {
895  mx = 0; my = diffY; rx = 0; ry = -diffY;
896  }
897  break;
898  }
899 
901  {
902  if ( ratio )
903  {
904  diffX = (( mBeginHandleHeight + diffY ) * ratio ) - mBeginHandleWidth;
905  mx = -diffX / 2; my = 0; rx = diffX; ry = diffY;
906  }
907  else
908  {
909  mx = 0; my = 0; rx = 0; ry = diffY;
910  }
911  break;
912  }
913 
914  //horizontal resize
916  {
917  if ( ratio )
918  {
919  diffY = (( mBeginHandleWidth - diffX ) / ratio ) - mBeginHandleHeight;
920  mx = diffX; my = -diffY / 2; rx = -diffX; ry = diffY;
921  }
922  else
923  {
924  mx = diffX, my = 0; rx = -diffX; ry = 0;
925  }
926  break;
927  }
928 
930  {
931  if ( ratio )
932  {
933  diffY = (( mBeginHandleWidth + diffX ) / ratio ) - mBeginHandleHeight;
934  mx = 0; my = -diffY / 2; rx = diffX; ry = diffY;
935  }
936  else
937  {
938  mx = 0; my = 0; rx = diffX, ry = 0;
939  }
940  break;
941  }
942 
943  //diagonal resize
945  {
946  if ( ratio )
947  {
948  //ratio locked resize
949  if (( mBeginHandleWidth - diffX ) / ( mBeginHandleHeight - diffY ) > ratio )
950  {
951  diffX = mBeginHandleWidth - (( mBeginHandleHeight - diffY ) * ratio );
952  }
953  else
954  {
955  diffY = mBeginHandleHeight - (( mBeginHandleWidth - diffX ) / ratio );
956  }
957  }
958  mx = diffX, my = diffY; rx = -diffX; ry = -diffY;
959  break;
960  }
961 
963  {
964  if ( ratio )
965  {
966  //ratio locked resize
967  if (( mBeginHandleWidth + diffX ) / ( mBeginHandleHeight + diffY ) > ratio )
968  {
969  diffX = (( mBeginHandleHeight + diffY ) * ratio ) - mBeginHandleWidth;
970  }
971  else
972  {
973  diffY = (( mBeginHandleWidth + diffX ) / ratio ) - mBeginHandleHeight;
974  }
975  }
976  mx = 0; my = 0; rx = diffX, ry = diffY;
977  break;
978  }
979 
981  {
982  if ( ratio )
983  {
984  //ratio locked resize
985  if (( mBeginHandleWidth + diffX ) / ( mBeginHandleHeight - diffY ) > ratio )
986  {
987  diffX = (( mBeginHandleHeight - diffY ) * ratio ) - mBeginHandleWidth;
988  }
989  else
990  {
991  diffY = mBeginHandleHeight - (( mBeginHandleWidth + diffX ) / ratio );
992  }
993  }
994  mx = 0; my = diffY, rx = diffX, ry = -diffY;
995  break;
996  }
997 
999  {
1000  if ( ratio )
1001  {
1002  //ratio locked resize
1003  if (( mBeginHandleWidth - diffX ) / ( mBeginHandleHeight + diffY ) > ratio )
1004  {
1005  diffX = mBeginHandleWidth - (( mBeginHandleHeight + diffY ) * ratio );
1006  }
1007  else
1008  {
1009  diffY = (( mBeginHandleWidth - diffX ) / ratio ) - mBeginHandleHeight;
1010  }
1011  }
1012  mx = diffX, my = 0; rx = -diffX; ry = diffY;
1013  break;
1014  }
1015 
1019  break;
1020  }
1021 
1022  //resizing from center of objects?
1023  if ( fromCenter )
1024  {
1025  my = -ry;
1026  mx = -rx;
1027  ry = 2 * ry;
1028  rx = 2 * rx;
1029  }
1030 
1031  //update selection handle rectangle
1032 
1033  //make sure selection handle size rectangle is normalized (ie, left coord < right coord)
1034  mResizeMoveX = mBeginHandleWidth + rx > 0 ? mx : mx + mBeginHandleWidth + rx;
1035  mResizeMoveY = mBeginHandleHeight + ry > 0 ? my : my + mBeginHandleHeight + ry;
1036 
1037  //calculate movement in scene coordinates
1038  QLineF translateLine = QLineF( 0, 0, mResizeMoveX, mResizeMoveY );
1039  translateLine.setAngle( translateLine.angle() - rotation() );
1040  QPointF sceneTranslate = translateLine.p2();
1041 
1042  //move selection handles
1043  QTransform itemTransform;
1044  itemTransform.translate( sceneTranslate.x(), sceneTranslate.y() );
1045  setTransform( itemTransform );
1046 
1047  //handle non-normalised resizes - eg, dragging the left handle so far to the right that it's past the right handle
1048  if ( mBeginHandleWidth + rx >= 0 && mBeginHandleHeight + ry >= 0 )
1049  {
1050  mResizeRect = QRectF( 0, 0, mBeginHandleWidth + rx, mBeginHandleHeight + ry );
1051  }
1052  else if ( mBeginHandleHeight + ry >= 0 )
1053  {
1054  mResizeRect = QRectF( QPointF( -( mBeginHandleWidth + rx ), 0 ), QPointF( 0, mBeginHandleHeight + ry ) );
1055  }
1056  else if ( mBeginHandleWidth + rx >= 0 )
1057  {
1058  mResizeRect = QRectF( QPointF( 0, -( mBeginHandleHeight + ry ) ), QPointF( mBeginHandleWidth + rx, 0 ) );
1059  }
1060  else
1061  {
1062  mResizeRect = QRectF( QPointF( -( mBeginHandleWidth + rx ), -( mBeginHandleHeight + ry ) ), QPointF( 0, 0 ) );
1063  }
1064 
1065  setRect( 0, 0, fabs( mBeginHandleWidth + rx ), fabs( mBeginHandleHeight + ry ) );
1066 
1067  //show current size of selection in status bar
1068  mComposition->setStatusMessage( QString( tr( "width: %1 mm height: %2 mm" ) ).arg( rect().width() ).arg( rect().height() ) );
1069 }
1070 
1071 QPointF QgsComposerMouseHandles::snapPoint( const QPointF& point, QgsComposerMouseHandles::SnapGuideMode mode )
1072 {
1073  //snap to grid
1074  QPointF snappedPoint = mComposition->snapPointToGrid( point );
1075 
1076  if ( snappedPoint != point ) //don't do align snap if grid snap has been done
1077  {
1078  deleteAlignItems();
1079  return snappedPoint;
1080  }
1081 
1082  //align item
1083  if ( !mComposition->alignmentSnap() && !mComposition->smartGuidesEnabled() )
1084  {
1085  return point;
1086  }
1087 
1088  double alignX = 0;
1089  double alignY = 0;
1090 
1091  //depending on the mode, we either snap just the single point, or all the bounds of the selection
1092  switch ( mode )
1093  {
1095  snappedPoint = alignItem( alignX, alignY, point.x(), point.y() );
1096  break;
1098  snappedPoint = alignPos( point, alignX, alignY );
1099  break;
1100  }
1101 
1102  if ( alignX != -1 )
1103  {
1104  QGraphicsLineItem* item = hAlignSnapItem();
1105  int numPages = mComposition->numPages();
1106  double yLineCoord = 300; //default in case there is no single page
1107  if ( numPages > 0 )
1108  {
1109  yLineCoord = mComposition->paperHeight() * numPages + mComposition->spaceBetweenPages() * ( numPages - 1 );
1110  }
1111  item->setLine( QLineF( alignX, 0, alignX, yLineCoord ) );
1112  item->show();
1113  }
1114  else
1115  {
1116  deleteHAlignSnapItem();
1117  }
1118  if ( alignY != -1 )
1119  {
1120  QGraphicsLineItem* item = vAlignSnapItem();
1121  item->setLine( QLineF( 0, alignY, mComposition->paperWidth(), alignY ) );
1122  item->show();
1123  }
1124  else
1125  {
1126  deleteVAlignSnapItem();
1127  }
1128  return snappedPoint;
1129 }
1130 
1131 QGraphicsLineItem* QgsComposerMouseHandles::hAlignSnapItem()
1132 {
1133  if ( !mHAlignSnapItem )
1134  {
1135  mHAlignSnapItem = new QGraphicsLineItem( 0 );
1136  mHAlignSnapItem->setPen( QPen( QColor( Qt::red ) ) );
1137  scene()->addItem( mHAlignSnapItem );
1138  mHAlignSnapItem->setZValue( 90 );
1139  }
1140  return mHAlignSnapItem;
1141 }
1142 
1143 QGraphicsLineItem* QgsComposerMouseHandles::vAlignSnapItem()
1144 {
1145  if ( !mVAlignSnapItem )
1146  {
1147  mVAlignSnapItem = new QGraphicsLineItem( 0 );
1148  mVAlignSnapItem->setPen( QPen( QColor( Qt::red ) ) );
1149  scene()->addItem( mVAlignSnapItem );
1150  mVAlignSnapItem->setZValue( 90 );
1151  }
1152  return mVAlignSnapItem;
1153 }
1154 
1155 void QgsComposerMouseHandles::deleteHAlignSnapItem()
1156 {
1157  if ( mHAlignSnapItem )
1158  {
1159  scene()->removeItem( mHAlignSnapItem );
1160  delete mHAlignSnapItem;
1161  mHAlignSnapItem = 0;
1162  }
1163 }
1164 
1165 void QgsComposerMouseHandles::deleteVAlignSnapItem()
1166 {
1167  if ( mVAlignSnapItem )
1168  {
1169  scene()->removeItem( mVAlignSnapItem );
1170  delete mVAlignSnapItem;
1171  mVAlignSnapItem = 0;
1172  }
1173 }
1174 
1175 void QgsComposerMouseHandles::deleteAlignItems()
1176 {
1177  deleteHAlignSnapItem();
1178  deleteVAlignSnapItem();
1179 }
1180 
1181 QPointF QgsComposerMouseHandles::alignItem( double& alignX, double& alignY, double unalignedX, double unalignedY )
1182 {
1183  double left = unalignedX;
1184  double right = left + rect().width();
1185  double midH = ( left + right ) / 2.0;
1186  double top = unalignedY;
1187  double bottom = top + rect().height();
1188  double midV = ( top + bottom ) / 2.0;
1189 
1190  QMap<double, const QgsComposerItem* > xAlignCoordinates;
1191  QMap<double, const QgsComposerItem* > yAlignCoordinates;
1192  collectAlignCoordinates( xAlignCoordinates, yAlignCoordinates );
1193 
1194  //find nearest matches x
1195  double xItemLeft = left; //new left coordinate of the item
1196  double xAlignCoord = 0;
1197  double smallestDiffX = DBL_MAX;
1198 
1199  checkNearestItem( left, xAlignCoordinates, smallestDiffX, 0, xItemLeft, xAlignCoord );
1200  checkNearestItem( midH, xAlignCoordinates, smallestDiffX, ( left - right ) / 2.0, xItemLeft, xAlignCoord );
1201  checkNearestItem( right, xAlignCoordinates, smallestDiffX, left - right, xItemLeft, xAlignCoord );
1202 
1203  //find nearest matches y
1204  double yItemTop = top; //new top coordinate of the item
1205  double yAlignCoord = 0;
1206  double smallestDiffY = DBL_MAX;
1207 
1208  checkNearestItem( top, yAlignCoordinates, smallestDiffY, 0, yItemTop, yAlignCoord );
1209  checkNearestItem( midV, yAlignCoordinates, smallestDiffY, ( top - bottom ) / 2.0, yItemTop, yAlignCoord );
1210  checkNearestItem( bottom, yAlignCoordinates, smallestDiffY, top - bottom, yItemTop, yAlignCoord );
1211 
1212  double xCoord = ( smallestDiffX < 5 ) ? xItemLeft : unalignedX;
1213  alignX = ( smallestDiffX < 5 ) ? xAlignCoord : -1;
1214  double yCoord = ( smallestDiffY < 5 ) ? yItemTop : unalignedY;
1215  alignY = ( smallestDiffY < 5 ) ? yAlignCoord : -1;
1216  return QPointF( xCoord, yCoord );
1217 }
1218 
1219 QPointF QgsComposerMouseHandles::alignPos( const QPointF& pos, double& alignX, double& alignY )
1220 {
1221  QMap<double, const QgsComposerItem* > xAlignCoordinates;
1222  QMap<double, const QgsComposerItem* > yAlignCoordinates;
1223  collectAlignCoordinates( xAlignCoordinates, yAlignCoordinates );
1224 
1225  double nearestX = pos.x();
1226  double nearestY = pos.y();
1227  if ( !nearestItem( xAlignCoordinates, pos.x(), nearestX )
1228  || !nearestItem( yAlignCoordinates, pos.y(), nearestY ) )
1229  {
1230  alignX = -1;
1231  alignY = -1;
1232  return pos;
1233  }
1234 
1235  //convert snap tolerance from pixels to mm
1236  double viewScaleFactor = graphicsView()->transform().m11();
1237  double alignThreshold = mComposition->snapTolerance() / viewScaleFactor;
1238 
1239  QPointF result( pos.x(), pos.y() );
1240  if ( fabs( nearestX - pos.x() ) < alignThreshold )
1241  {
1242  result.setX( nearestX );
1243  alignX = nearestX;
1244  }
1245  else
1246  {
1247  alignX = -1;
1248  }
1249 
1250  if ( fabs( nearestY - pos.y() ) < alignThreshold )
1251  {
1252  result.setY( nearestY );
1253  alignY = nearestY;
1254  }
1255  else
1256  {
1257  alignY = -1;
1258  }
1259  return result;
1260 }
1261 
1262 void QgsComposerMouseHandles::collectAlignCoordinates( QMap< double, const QgsComposerItem* >& alignCoordsX, QMap< double, const QgsComposerItem* >& alignCoordsY )
1263 {
1264  alignCoordsX.clear();
1265  alignCoordsY.clear();
1266 
1267  if ( mComposition->smartGuidesEnabled() )
1268  {
1269  QList<QGraphicsItem *> itemList = mComposition->items();
1270  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
1271  for ( ; itemIt != itemList.end(); ++itemIt )
1272  {
1273  const QgsComposerItem* currentItem = dynamic_cast<const QgsComposerItem *>( *itemIt );
1274  //don't snap to selected items, since they're the ones that will be snapping to something else
1275  //also ignore group members - only snap to bounds of group itself
1276  //also ignore hidden items
1277  if ( !currentItem || currentItem->selected() || currentItem->isGroupMember() || !currentItem->isVisible() )
1278  {
1279  continue;
1280  }
1281  QRectF itemRect;
1282  if ( dynamic_cast<const QgsPaperItem *>( *itemIt ) )
1283  {
1284  //if snapping to paper use the paper item's rect rather then the bounding rect,
1285  //since we want to snap to the page edge and not any outlines drawn around the page
1286  itemRect = currentItem->mapRectToScene( currentItem->rect() );
1287  }
1288  else
1289  {
1290  itemRect = currentItem->mapRectToScene( currentItem->rectWithFrame() );
1291  }
1292  alignCoordsX.insert( itemRect.left(), currentItem );
1293  alignCoordsX.insert( itemRect.right(), currentItem );
1294  alignCoordsX.insert( itemRect.center().x(), currentItem );
1295  alignCoordsY.insert( itemRect.top(), currentItem );
1296  alignCoordsY.insert( itemRect.center().y(), currentItem );
1297  alignCoordsY.insert( itemRect.bottom(), currentItem );
1298  }
1299  }
1300 
1301  //arbitrary snap lines
1302  if ( mComposition->alignmentSnap() )
1303  {
1304  QList< QGraphicsLineItem* >::const_iterator sIt = mComposition->snapLines()->constBegin();
1305  for ( ; sIt != mComposition->snapLines()->constEnd(); ++sIt )
1306  {
1307  double x = ( *sIt )->line().x1();
1308  double y = ( *sIt )->line().y1();
1309  if ( qgsDoubleNear( y, 0.0 ) )
1310  {
1311  alignCoordsX.insert( x, 0 );
1312  }
1313  else
1314  {
1315  alignCoordsY.insert( y, 0 );
1316  }
1317  }
1318  }
1319 }
1320 
1321 void QgsComposerMouseHandles::checkNearestItem( double checkCoord, const QMap< double, const QgsComposerItem* >& alignCoords, double& smallestDiff, double itemCoordOffset, double& itemCoord, double& alignCoord )
1322 {
1323  double currentCoord = 0;
1324  if ( !nearestItem( alignCoords, checkCoord, currentCoord ) )
1325  {
1326  return;
1327  }
1328 
1329  double currentDiff = fabs( checkCoord - currentCoord );
1330  //convert snap tolerance from pixels to mm
1331  double viewScaleFactor = graphicsView()->transform().m11();
1332  double alignThreshold = mComposition->snapTolerance() / viewScaleFactor;
1333 
1334  if ( currentDiff < alignThreshold && currentDiff < smallestDiff )
1335  {
1336  itemCoord = currentCoord + itemCoordOffset;
1337  alignCoord = currentCoord;
1338  smallestDiff = currentDiff;
1339  }
1340 }
1341 
1342 bool QgsComposerMouseHandles::nearestItem( const QMap< double, const QgsComposerItem* >& coords, double value, double& nearestValue ) const
1343 {
1344  if ( coords.size() < 1 )
1345  {
1346  return false;
1347  }
1348 
1349  QMap< double, const QgsComposerItem* >::const_iterator it = coords.lowerBound( value );
1350  if ( it == coords.constBegin() ) //value smaller than first map value
1351  {
1352  nearestValue = it.key();
1353  return true;
1354  }
1355  else if ( it == coords.constEnd() ) //value larger than last map value
1356  {
1357  --it;
1358  nearestValue = it.key();
1359  return true;
1360  }
1361  else
1362  {
1363  //get smaller value and larger value and return the closer one
1364  double upperVal = it.key();
1365  --it;
1366  double lowerVal = it.key();
1367 
1368  double lowerDiff = value - lowerVal;
1369  double upperDiff = upperVal - value;
1370  if ( lowerDiff < upperDiff )
1371  {
1372  nearestValue = lowerVal;
1373  return true;
1374  }
1375  else
1376  {
1377  nearestValue = upperVal;
1378  return true;
1379  }
1380  }
1381 }
1382