QGIS API Documentation  2.18.21-Las Palmas (9fba24a)
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 : [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 <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( nullptr )
35  , QGraphicsRectItem( nullptr )
36  , mComposition( composition )
37  , mGraphicsView( nullptr )
38  , mCurrentMouseMoveAction( NoAction )
39  , mBeginHandleWidth( 0 )
40  , mBeginHandleHeight( 0 )
41  , mResizeMoveX( 0 )
42  , mResizeMoveY( 0 )
43  , mIsDragging( false )
44  , mIsResizing( false )
45  , mHAlignSnapItem( nullptr )
46  , mVAlignSnapItem( nullptr )
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.isEmpty() )
73  {
74  mGraphicsView = viewList.at( 0 );
75  return mGraphicsView;
76  }
77  }
78 
79  //no view attached to composition
80  return nullptr;
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.isEmpty() )
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 
198  // drawPolygon causes issues on windows - corners of path may be missing resulting in triangles being drawn
199  // instead of rectangles! (Same cause as #13343)
200  QPainterPath path;
201  path.addPolygon( itemBounds );
202  painter->drawPath( path );
203  }
204  painter->restore();
205 }
206 
208 {
209  //listen out for selected items' size and rotation changed signals
210  QList<QGraphicsItem *> itemList = composition()->items();
211  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
212  for ( ; itemIt != itemList.end(); ++itemIt )
213  {
214  QgsComposerItem* item = dynamic_cast<QgsComposerItem *>( *itemIt );
215  if ( item )
216  {
217  if ( item->selected() )
218  {
219  QObject::connect( item, SIGNAL( sizeChanged() ), this, SLOT( selectedItemSizeChanged() ) );
220  QObject::connect( item, SIGNAL( itemRotationChanged( double ) ), this, SLOT( selectedItemRotationChanged() ) );
221  QObject::connect( item, SIGNAL( frameChanged() ), this, SLOT( selectedItemSizeChanged() ) );
222  QObject::connect( item, SIGNAL( lockChanged() ), this, SLOT( selectedItemSizeChanged() ) );
223  }
224  else
225  {
226  QObject::disconnect( item, SIGNAL( sizeChanged() ), this, nullptr );
227  QObject::disconnect( item, SIGNAL( itemRotationChanged( double ) ), this, nullptr );
228  QObject::disconnect( item, SIGNAL( frameChanged() ), this, nullptr );
229  QObject::disconnect( item, SIGNAL( lockChanged() ), this, nullptr );
230  }
231  }
232  }
233 
234  resetStatusBar();
235  updateHandles();
236 }
237 
239 {
240  if ( !mIsDragging && !mIsResizing )
241  {
242  //only required for non-mouse initiated size changes
243  updateHandles();
244  }
245 }
246 
248 {
249  if ( !mIsDragging && !mIsResizing )
250  {
251  //only required for non-mouse initiated rotation changes
252  updateHandles();
253  }
254 }
255 
256 void QgsComposerMouseHandles::updateHandles()
257 {
258  //recalculate size and position of handle item
259 
260  //first check to see if any items are selected
261  QList<QgsComposerItem*> selectedItems = mComposition->selectedComposerItems( false );
262  if ( !selectedItems.isEmpty() )
263  {
264  //one or more items are selected, get bounds of all selected items
265 
266  //update rotation of handle object
267  double rotation;
268  if ( selectionRotation( rotation ) )
269  {
270  //all items share a common rotation value, so we rotate the mouse handles to match
271  setRotation( rotation );
272  }
273  else
274  {
275  //items have varying rotation values - we can't rotate the mouse handles to match
276  setRotation( 0 );
277  }
278 
279  //get bounds of all selected items
280  QRectF newHandleBounds = selectionBounds();
281 
282  //update size and position of handle object
283  setRect( 0, 0, newHandleBounds.width(), newHandleBounds.height() );
284  setPos( mapToScene( newHandleBounds.topLeft() ) );
285 
286  show();
287  }
288  else
289  {
290  //no items selected, hide handles
291  hide();
292  }
293  //force redraw
294  update();
295 }
296 
297 QRectF QgsComposerMouseHandles::selectionBounds() const
298 {
299  //calculate bounds of all currently selected items in mouse handle coordinate system
300  QList<QgsComposerItem*> selectedItems = mComposition->selectedComposerItems( false );
301  QList<QgsComposerItem*>::iterator itemIter = selectedItems.begin();
302 
303  //start with handle bounds of first selected item
304  QRectF bounds = mapFromItem(( *itemIter ), ( *itemIter )->rectWithFrame() ).boundingRect();
305 
306  //iterate through remaining items, expanding the bounds as required
307  for ( ++itemIter; itemIter != selectedItems.end(); ++itemIter )
308  {
309  bounds = bounds.united( mapFromItem(( *itemIter ), ( *itemIter )->rectWithFrame() ).boundingRect() );
310  }
311 
312  return bounds;
313 }
314 
315 bool QgsComposerMouseHandles::selectionRotation( double & rotation ) const
316 {
317  //check if all selected items have same rotation
318  QList<QgsComposerItem*> selectedItems = mComposition->selectedComposerItems( false );
319  QList<QgsComposerItem*>::iterator itemIter = selectedItems.begin();
320 
321  //start with rotation of first selected item
322  double firstItemRotation = ( *itemIter )->itemRotation();
323 
324  //iterate through remaining items, checking if they have same rotation
325  for ( ++itemIter; itemIter != selectedItems.end(); ++itemIter )
326  {
327  if ( !qgsDoubleNear(( *itemIter )->itemRotation(), firstItemRotation ) )
328  {
329  //item has a different rotation, so return false
330  return false;
331  }
332  }
333 
334  //all items have the same rotation, so set the rotation variable and return true
335  rotation = firstItemRotation;
336  return true;
337 }
338 
339 double QgsComposerMouseHandles::rectHandlerBorderTolerance()
340 {
341  //calculate size for resize handles
342  //get view scale factor
343  double viewScaleFactor = graphicsView()->transform().m11();
344 
345  //size of handle boxes depends on zoom level in composer view
346  double rectHandlerSize = 10.0 / viewScaleFactor;
347 
348  //make sure the boxes don't get too large
349  if ( rectHandlerSize > ( rect().width() / 3 ) )
350  {
351  rectHandlerSize = rect().width() / 3;
352  }
353  if ( rectHandlerSize > ( rect().height() / 3 ) )
354  {
355  rectHandlerSize = rect().height() / 3;
356  }
357  return rectHandlerSize;
358 }
359 
360 Qt::CursorShape QgsComposerMouseHandles::cursorForPosition( QPointF itemCoordPos )
361 {
362  QgsComposerMouseHandles::MouseAction mouseAction = mouseActionForPosition( itemCoordPos );
363  switch ( mouseAction )
364  {
365  case NoAction:
366  return Qt::ForbiddenCursor;
367  case MoveItem:
368  return Qt::SizeAllCursor;
369  case ResizeUp:
370  case ResizeDown:
371  //account for rotation
372  if (( rotation() <= 22.5 || rotation() >= 337.5 ) || ( rotation() >= 157.5 && rotation() <= 202.5 ) )
373  {
374  return Qt::SizeVerCursor;
375  }
376  else if (( rotation() >= 22.5 && rotation() <= 67.5 ) || ( rotation() >= 202.5 && rotation() <= 247.5 ) )
377  {
378  return Qt::SizeBDiagCursor;
379  }
380  else if (( rotation() >= 67.5 && rotation() <= 112.5 ) || ( rotation() >= 247.5 && rotation() <= 292.5 ) )
381  {
382  return Qt::SizeHorCursor;
383  }
384  else
385  {
386  return Qt::SizeFDiagCursor;
387  }
388  case ResizeLeft:
389  case ResizeRight:
390  //account for rotation
391  if (( rotation() <= 22.5 || rotation() >= 337.5 ) || ( rotation() >= 157.5 && rotation() <= 202.5 ) )
392  {
393  return Qt::SizeHorCursor;
394  }
395  else if (( rotation() >= 22.5 && rotation() <= 67.5 ) || ( rotation() >= 202.5 && rotation() <= 247.5 ) )
396  {
397  return Qt::SizeFDiagCursor;
398  }
399  else if (( rotation() >= 67.5 && rotation() <= 112.5 ) || ( rotation() >= 247.5 && rotation() <= 292.5 ) )
400  {
401  return Qt::SizeVerCursor;
402  }
403  else
404  {
405  return Qt::SizeBDiagCursor;
406  }
407 
408  case ResizeLeftUp:
409  case ResizeRightDown:
410  //account for rotation
411  if (( rotation() <= 22.5 || rotation() >= 337.5 ) || ( rotation() >= 157.5 && rotation() <= 202.5 ) )
412  {
413  return Qt::SizeFDiagCursor;
414  }
415  else if (( rotation() >= 22.5 && rotation() <= 67.5 ) || ( rotation() >= 202.5 && rotation() <= 247.5 ) )
416  {
417  return Qt::SizeVerCursor;
418  }
419  else if (( rotation() >= 67.5 && rotation() <= 112.5 ) || ( rotation() >= 247.5 && rotation() <= 292.5 ) )
420  {
421  return Qt::SizeBDiagCursor;
422  }
423  else
424  {
425  return Qt::SizeHorCursor;
426  }
427  case ResizeRightUp:
428  case ResizeLeftDown:
429  //account for rotation
430  if (( rotation() <= 22.5 || rotation() >= 337.5 ) || ( rotation() >= 157.5 && rotation() <= 202.5 ) )
431  {
432  return Qt::SizeBDiagCursor;
433  }
434  else if (( rotation() >= 22.5 && rotation() <= 67.5 ) || ( rotation() >= 202.5 && rotation() <= 247.5 ) )
435  {
436  return Qt::SizeHorCursor;
437  }
438  else if (( rotation() >= 67.5 && rotation() <= 112.5 ) || ( rotation() >= 247.5 && rotation() <= 292.5 ) )
439  {
440  return Qt::SizeFDiagCursor;
441  }
442  else
443  {
444  return Qt::SizeVerCursor;
445  }
446  case SelectItem:
447  default:
448  return Qt::ArrowCursor;
449  }
450 }
451 
452 QgsComposerMouseHandles::MouseAction QgsComposerMouseHandles::mouseActionForPosition( QPointF itemCoordPos )
453 {
454  bool nearLeftBorder = false;
455  bool nearRightBorder = false;
456  bool nearLowerBorder = false;
457  bool nearUpperBorder = false;
458 
459  bool withinWidth = false;
460  bool withinHeight = false;
461  if ( itemCoordPos.x() >= 0 && itemCoordPos.x() <= rect().width() )
462  {
463  withinWidth = true;
464  }
465  if ( itemCoordPos.y() >= 0 && itemCoordPos.y() <= rect().height() )
466  {
467  withinHeight = true;
468  }
469 
470  double borderTolerance = rectHandlerBorderTolerance();
471 
472  if ( itemCoordPos.x() >= 0 && itemCoordPos.x() < borderTolerance )
473  {
474  nearLeftBorder = true;
475  }
476  if ( itemCoordPos.y() >= 0 && itemCoordPos.y() < borderTolerance )
477  {
478  nearUpperBorder = true;
479  }
480  if ( itemCoordPos.x() <= rect().width() && itemCoordPos.x() > ( rect().width() - borderTolerance ) )
481  {
482  nearRightBorder = true;
483  }
484  if ( itemCoordPos.y() <= rect().height() && itemCoordPos.y() > ( rect().height() - borderTolerance ) )
485  {
486  nearLowerBorder = true;
487  }
488 
489  if ( nearLeftBorder && nearUpperBorder )
490  {
492  }
493  else if ( nearLeftBorder && nearLowerBorder )
494  {
496  }
497  else if ( nearRightBorder && nearUpperBorder )
498  {
500  }
501  else if ( nearRightBorder && nearLowerBorder )
502  {
504  }
505  else if ( nearLeftBorder && withinHeight )
506  {
508  }
509  else if ( nearRightBorder && withinHeight )
510  {
512  }
513  else if ( nearUpperBorder && withinWidth )
514  {
516  }
517  else if ( nearLowerBorder && withinWidth )
518  {
520  }
521 
522  //find out if cursor position is over a selected item
523  QPointF scenePoint = mapToScene( itemCoordPos );
524  QList<QGraphicsItem *> itemsAtCursorPos = mComposition->items( scenePoint );
525  if ( itemsAtCursorPos.isEmpty() )
526  {
527  //no items at cursor position
529  }
530  QList<QGraphicsItem*>::iterator itemIter = itemsAtCursorPos.begin();
531  for ( ; itemIter != itemsAtCursorPos.end(); ++itemIter )
532  {
533  QgsComposerItem* item = dynamic_cast<QgsComposerItem *>(( *itemIter ) );
534  if ( item && item->selected() )
535  {
536  //cursor is over a selected composer item
538  }
539  }
540 
541  //default
543 }
544 
546 {
547  // convert sceneCoordPos to item coordinates
548  QPointF itemPos = mapFromScene( sceneCoordPos );
549  return mouseActionForPosition( itemPos );
550 }
551 
553 {
554  setViewportCursor( cursorForPosition( event->pos() ) );
555 }
556 
558 {
559  Q_UNUSED( event );
560  setViewportCursor( Qt::ArrowCursor );
561 }
562 
563 void QgsComposerMouseHandles::setViewportCursor( Qt::CursorShape cursor )
564 {
565  //workaround qt bug #3732 by setting cursor for QGraphicsView viewport,
566  //rather then setting it directly here
567 
568  if ( !mComposition->preventCursorChange() )
569  {
570  graphicsView()->viewport()->setCursor( cursor );
571  }
572 }
573 
575 {
576  if ( mIsDragging )
577  {
578  //currently dragging a selection
579  //if shift depressed, constrain movement to horizontal/vertical
580  //if control depressed, ignore snapping
581  dragMouseMove( event->lastScenePos(), event->modifiers() & Qt::ShiftModifier, event->modifiers() & Qt::ControlModifier );
582  }
583  else if ( mIsResizing )
584  {
585  //currently resizing a selection
586  //lock aspect ratio if shift depressed
587  //resize from center if alt depressed
588  resizeMouseMove( event->lastScenePos(), event->modifiers() & Qt::ShiftModifier, event->modifiers() & Qt::AltModifier );
589  }
590 
591  mLastMouseEventPos = event->lastScenePos();
592 }
593 
595 {
596  QPointF mouseMoveStopPoint = event->lastScenePos();
597  double diffX = mouseMoveStopPoint.x() - mMouseMoveStartPos.x();
598  double diffY = mouseMoveStopPoint.y() - mMouseMoveStartPos.y();
599 
600  //it was only a click
601  if ( qAbs( diffX ) < std::numeric_limits<double>::min() && qAbs( diffY ) < std::numeric_limits<double>::min() )
602  {
603  mIsDragging = false;
604  mIsResizing = false;
605  update();
606  return;
607  }
608 
609  if ( mCurrentMouseMoveAction == QgsComposerMouseHandles::MoveItem )
610  {
611  //move selected items
612  QUndoCommand* parentCommand = new QUndoCommand( tr( "Change item position" ) );
613 
614  QPointF mEndHandleMovePos = scenePos();
615 
616  //move all selected items
617  QList<QgsComposerItem*> selectedItems = mComposition->selectedComposerItems( false );
618  QList<QgsComposerItem*>::iterator itemIter = selectedItems.begin();
619  for ( ; itemIter != selectedItems.end(); ++itemIter )
620  {
621  if (( *itemIter )->positionLock() || (( *itemIter )->flags() & QGraphicsItem::ItemIsSelectable ) == 0 )
622  {
623  //don't move locked items
624  continue;
625  }
626  QgsComposerItemCommand* subcommand = new QgsComposerItemCommand( *itemIter, "", parentCommand );
627  subcommand->savePreviousState();
628  ( *itemIter )->move( mEndHandleMovePos.x() - mBeginHandlePos.x(), mEndHandleMovePos.y() - mBeginHandlePos.y() );
629  subcommand->saveAfterState();
630  }
631  mComposition->undoStack()->push( parentCommand );
632  QgsProject::instance()->setDirty( true );
633  }
634  else if ( mCurrentMouseMoveAction != QgsComposerMouseHandles::NoAction )
635  {
636  //resize selected items
637  QUndoCommand* parentCommand = new QUndoCommand( tr( "Change item size" ) );
638 
639  //resize all selected items
640  QList<QgsComposerItem*> selectedItems = mComposition->selectedComposerItems( false );
641  QList<QgsComposerItem*>::iterator itemIter = selectedItems.begin();
642  for ( ; itemIter != selectedItems.end(); ++itemIter )
643  {
644  if (( *itemIter )->positionLock() || (( *itemIter )->flags() & QGraphicsItem::ItemIsSelectable ) == 0 )
645  {
646  //don't resize locked items or unselectable items (eg, items which make up an item group)
647  continue;
648  }
649  QgsComposerItemCommand* subcommand = new QgsComposerItemCommand( *itemIter, "", parentCommand );
650  subcommand->savePreviousState();
651 
652  QRectF itemRect;
653  if ( selectedItems.size() == 1 )
654  {
655  //only a single item is selected, so set its size to the final resized mouse handle size
656  itemRect = mResizeRect;
657  }
658  else
659  {
660  //multiple items selected, so each needs to be scaled relatively to the final size of the mouse handles
661  itemRect = mapRectFromItem(( *itemIter ), ( *itemIter )->rectWithFrame() );
662  QgsComposerUtils::relativeResizeRect( itemRect, QRectF( -mResizeMoveX, -mResizeMoveY, mBeginHandleWidth, mBeginHandleHeight ), mResizeRect );
663  }
664 
665  itemRect = itemRect.normalized();
666  QPointF newPos = mapToScene( itemRect.topLeft() );
667  ( *itemIter )->setItemPosition( newPos.x(), newPos.y(), itemRect.width(), itemRect.height(), QgsComposerItem::UpperLeft, true );
668 
669  subcommand->saveAfterState();
670  }
671  mComposition->undoStack()->push( parentCommand );
672  QgsProject::instance()->setDirty( true );
673  }
674 
675  deleteAlignItems();
676 
677  if ( mIsDragging )
678  {
679  mIsDragging = false;
680  }
681  if ( mIsResizing )
682  {
683  mIsResizing = false;
684  }
685 
686  //reset default action
687  mCurrentMouseMoveAction = QgsComposerMouseHandles::MoveItem;
688  setViewportCursor( Qt::ArrowCursor );
689  //redraw handles
690  resetTransform();
691  updateHandles();
692  //reset status bar message
693  resetStatusBar();
694 }
695 
696 void QgsComposerMouseHandles::resetStatusBar()
697 {
698  QList<QgsComposerItem*> selectedItems = mComposition->selectedComposerItems( false );
699  int selectedCount = selectedItems.size();
700  if ( selectedCount > 1 )
701  {
702  //set status bar message to count of selected items
703  mComposition->setStatusMessage( QString( tr( "%1 items selected" ) ).arg( selectedCount ) );
704  }
705  else if ( selectedCount == 1 )
706  {
707  //set status bar message to count of selected items
708  mComposition->setStatusMessage( tr( "1 item selected" ) );
709  }
710  else
711  {
712  //clear status bar message
713  mComposition->setStatusMessage( QString() );
714  }
715 }
716 
718 {
719  //save current cursor position
720  mMouseMoveStartPos = event->lastScenePos();
721  mLastMouseEventPos = event->lastScenePos();
722  //save current item geometry
723  mBeginMouseEventPos = event->lastScenePos();
724  mBeginHandlePos = scenePos();
725  mBeginHandleWidth = rect().width();
726  mBeginHandleHeight = rect().height();
727  //type of mouse move action
728  mCurrentMouseMoveAction = mouseActionForPosition( event->pos() );
729 
730  deleteAlignItems();
731 
732  if ( mCurrentMouseMoveAction == QgsComposerMouseHandles::MoveItem )
733  {
734  //moving items
735  mIsDragging = true;
736  }
737  else if ( mCurrentMouseMoveAction != QgsComposerMouseHandles::SelectItem &&
738  mCurrentMouseMoveAction != QgsComposerMouseHandles::NoAction )
739  {
740  //resizing items
741  mIsResizing = true;
742  mResizeRect = QRectF( 0, 0, mBeginHandleWidth, mBeginHandleHeight );
743  mResizeMoveX = 0;
744  mResizeMoveY = 0;
745  mCursorOffset = calcCursorEdgeOffset( mMouseMoveStartPos );
746 
747  }
748 
749 }
750 
752 {
753  Q_UNUSED( event );
754 }
755 
756 QSizeF QgsComposerMouseHandles::calcCursorEdgeOffset( QPointF cursorPos )
757 {
758  //find offset between cursor position and actual edge of item
759  QPointF sceneMousePos = mapFromScene( cursorPos );
760 
761  switch ( mCurrentMouseMoveAction )
762  {
763  //vertical resize
765  return QSizeF( 0, sceneMousePos.y() );
766 
768  return QSizeF( 0, sceneMousePos.y() - rect().height() );
769 
770  //horizontal resize
772  return QSizeF( sceneMousePos.x(), 0 );
773 
775  return QSizeF( sceneMousePos.x() - rect().width(), 0 );
776 
777  //diagonal resize
779  return QSizeF( sceneMousePos.x(), sceneMousePos.y() );
780 
782  return QSizeF( sceneMousePos.x() - rect().width(), sceneMousePos.y() - rect().height() );
783 
785  return QSizeF( sceneMousePos.x() - rect().width(), sceneMousePos.y() );
786 
788  return QSizeF( sceneMousePos.x(), sceneMousePos.y() - rect().height() );
789 
790  default:
791  return QSizeF( 0, 0 );
792  }
793 
794 }
795 
796 void QgsComposerMouseHandles::dragMouseMove( QPointF currentPosition, bool lockMovement, bool preventSnap )
797 {
798  if ( !mComposition )
799  {
800  return;
801  }
802 
803  //calculate total amount of mouse movement since drag began
804  double moveX = currentPosition.x() - mBeginMouseEventPos.x();
805  double moveY = currentPosition.y() - mBeginMouseEventPos.y();
806 
807  //find target position before snapping (in scene coordinates)
808  QPointF upperLeftPoint( mBeginHandlePos.x() + moveX, mBeginHandlePos.y() + moveY );
809 
810  QPointF snappedLeftPoint;
811  //no snapping for rotated items for now
812  if ( !preventSnap && qgsDoubleNear( rotation(), 0.0 ) )
813  {
814  //snap to grid and guides
815  snappedLeftPoint = snapPoint( upperLeftPoint, QgsComposerMouseHandles::Item );
816  }
817  else
818  {
819  //no snapping
820  snappedLeftPoint = upperLeftPoint;
821  deleteAlignItems();
822  }
823 
824  //calculate total shift for item from beginning of drag operation to current position
825  double moveRectX = snappedLeftPoint.x() - mBeginHandlePos.x();
826  double moveRectY = snappedLeftPoint.y() - mBeginHandlePos.y();
827 
828  if ( lockMovement )
829  {
830  //constrained (shift) moving should lock to horizontal/vertical movement
831  //reset the smaller of the x/y movements
832  if ( qAbs( moveRectX ) <= qAbs( moveRectY ) )
833  {
834  moveRectX = 0;
835  }
836  else
837  {
838  moveRectY = 0;
839  }
840  }
841 
842  //shift handle item to new position
843  QTransform moveTransform;
844  moveTransform.translate( moveRectX, moveRectY );
845  setTransform( moveTransform );
846  //show current displacement of selection in status bar
847  mComposition->setStatusMessage( QString( tr( "dx: %1 mm dy: %2 mm" ) ).arg( moveRectX ).arg( moveRectY ) );
848 }
849 
850 void QgsComposerMouseHandles::resizeMouseMove( QPointF currentPosition, bool lockRatio, bool fromCenter )
851 {
852 
853  if ( !mComposition )
854  {
855  return;
856  }
857 
858  double mx = 0.0, my = 0.0, rx = 0.0, ry = 0.0;
859 
860  QPointF beginMousePos;
861  QPointF finalPosition;
862  if ( qgsDoubleNear( rotation(), 0.0 ) )
863  {
864  //snapping only occurs if handles are not rotated for now
865 
866  //subtract cursor edge offset from begin mouse event and current cursor position, so that snapping occurs to edge of mouse handles
867  //rather then cursor position
868  beginMousePos = mapFromScene( QPointF( mBeginMouseEventPos.x() - mCursorOffset.width(), mBeginMouseEventPos.y() - mCursorOffset.height() ) );
869  QPointF snappedPosition = snapPoint( QPointF( currentPosition.x() - mCursorOffset.width(), currentPosition.y() - mCursorOffset.height() ), QgsComposerMouseHandles::Point );
870  finalPosition = mapFromScene( snappedPosition );
871  }
872  else
873  {
874  //no snapping for rotated items for now
875  beginMousePos = mapFromScene( mBeginMouseEventPos );
876  finalPosition = mapFromScene( currentPosition );
877  }
878 
879  double diffX = finalPosition.x() - beginMousePos.x();
880  double diffY = finalPosition.y() - beginMousePos.y();
881 
882  double ratio = 0;
883  if ( lockRatio && !qgsDoubleNear( mBeginHandleHeight, 0.0 ) )
884  {
885  ratio = mBeginHandleWidth / mBeginHandleHeight;
886  }
887 
888  switch ( mCurrentMouseMoveAction )
889  {
890  //vertical resize
892  {
893  if ( ratio )
894  {
895  diffX = (( mBeginHandleHeight - diffY ) * ratio ) - mBeginHandleWidth;
896  mx = -diffX / 2;
897  my = diffY;
898  rx = diffX;
899  ry = -diffY;
900  }
901  else
902  {
903  mx = 0;
904  my = diffY;
905  rx = 0;
906  ry = -diffY;
907  }
908  break;
909  }
910 
912  {
913  if ( ratio )
914  {
915  diffX = (( mBeginHandleHeight + diffY ) * ratio ) - mBeginHandleWidth;
916  mx = -diffX / 2;
917  my = 0;
918  rx = diffX;
919  ry = diffY;
920  }
921  else
922  {
923  mx = 0;
924  my = 0;
925  rx = 0;
926  ry = diffY;
927  }
928  break;
929  }
930 
931  //horizontal resize
933  {
934  if ( ratio )
935  {
936  diffY = (( mBeginHandleWidth - diffX ) / ratio ) - mBeginHandleHeight;
937  mx = diffX;
938  my = -diffY / 2;
939  rx = -diffX;
940  ry = diffY;
941  }
942  else
943  {
944  mx = diffX, my = 0;
945  rx = -diffX;
946  ry = 0;
947  }
948  break;
949  }
950 
952  {
953  if ( ratio )
954  {
955  diffY = (( mBeginHandleWidth + diffX ) / ratio ) - mBeginHandleHeight;
956  mx = 0;
957  my = -diffY / 2;
958  rx = diffX;
959  ry = diffY;
960  }
961  else
962  {
963  mx = 0;
964  my = 0;
965  rx = diffX, ry = 0;
966  }
967  break;
968  }
969 
970  //diagonal resize
972  {
973  if ( ratio )
974  {
975  //ratio locked resize
976  if (( mBeginHandleWidth - diffX ) / ( mBeginHandleHeight - diffY ) > ratio )
977  {
978  diffX = mBeginHandleWidth - (( mBeginHandleHeight - diffY ) * ratio );
979  }
980  else
981  {
982  diffY = mBeginHandleHeight - (( mBeginHandleWidth - diffX ) / ratio );
983  }
984  }
985  mx = diffX, my = diffY;
986  rx = -diffX;
987  ry = -diffY;
988  break;
989  }
990 
992  {
993  if ( ratio )
994  {
995  //ratio locked resize
996  if (( mBeginHandleWidth + diffX ) / ( mBeginHandleHeight + diffY ) > ratio )
997  {
998  diffX = (( mBeginHandleHeight + diffY ) * ratio ) - mBeginHandleWidth;
999  }
1000  else
1001  {
1002  diffY = (( mBeginHandleWidth + diffX ) / ratio ) - mBeginHandleHeight;
1003  }
1004  }
1005  mx = 0;
1006  my = 0;
1007  rx = diffX, ry = diffY;
1008  break;
1009  }
1010 
1012  {
1013  if ( ratio )
1014  {
1015  //ratio locked resize
1016  if (( mBeginHandleWidth + diffX ) / ( mBeginHandleHeight - diffY ) > ratio )
1017  {
1018  diffX = (( mBeginHandleHeight - diffY ) * ratio ) - mBeginHandleWidth;
1019  }
1020  else
1021  {
1022  diffY = mBeginHandleHeight - (( mBeginHandleWidth + diffX ) / ratio );
1023  }
1024  }
1025  mx = 0;
1026  my = diffY, rx = diffX, ry = -diffY;
1027  break;
1028  }
1029 
1031  {
1032  if ( ratio )
1033  {
1034  //ratio locked resize
1035  if (( mBeginHandleWidth - diffX ) / ( mBeginHandleHeight + diffY ) > ratio )
1036  {
1037  diffX = mBeginHandleWidth - (( mBeginHandleHeight + diffY ) * ratio );
1038  }
1039  else
1040  {
1041  diffY = (( mBeginHandleWidth - diffX ) / ratio ) - mBeginHandleHeight;
1042  }
1043  }
1044  mx = diffX, my = 0;
1045  rx = -diffX;
1046  ry = diffY;
1047  break;
1048  }
1049 
1053  break;
1054  }
1055 
1056  //resizing from center of objects?
1057  if ( fromCenter )
1058  {
1059  my = -ry;
1060  mx = -rx;
1061  ry = 2 * ry;
1062  rx = 2 * rx;
1063  }
1064 
1065  //update selection handle rectangle
1066 
1067  //make sure selection handle size rectangle is normalized (ie, left coord < right coord)
1068  mResizeMoveX = mBeginHandleWidth + rx > 0 ? mx : mx + mBeginHandleWidth + rx;
1069  mResizeMoveY = mBeginHandleHeight + ry > 0 ? my : my + mBeginHandleHeight + ry;
1070 
1071  //calculate movement in scene coordinates
1072  QLineF translateLine = QLineF( 0, 0, mResizeMoveX, mResizeMoveY );
1073  translateLine.setAngle( translateLine.angle() - rotation() );
1074  QPointF sceneTranslate = translateLine.p2();
1075 
1076  //move selection handles
1078  itemTransform.translate( sceneTranslate.x(), sceneTranslate.y() );
1079  setTransform( itemTransform );
1080 
1081  //handle non-normalised resizes - eg, dragging the left handle so far to the right that it's past the right handle
1082  if ( mBeginHandleWidth + rx >= 0 && mBeginHandleHeight + ry >= 0 )
1083  {
1084  mResizeRect = QRectF( 0, 0, mBeginHandleWidth + rx, mBeginHandleHeight + ry );
1085  }
1086  else if ( mBeginHandleHeight + ry >= 0 )
1087  {
1088  mResizeRect = QRectF( QPointF( -( mBeginHandleWidth + rx ), 0 ), QPointF( 0, mBeginHandleHeight + ry ) );
1089  }
1090  else if ( mBeginHandleWidth + rx >= 0 )
1091  {
1092  mResizeRect = QRectF( QPointF( 0, -( mBeginHandleHeight + ry ) ), QPointF( mBeginHandleWidth + rx, 0 ) );
1093  }
1094  else
1095  {
1096  mResizeRect = QRectF( QPointF( -( mBeginHandleWidth + rx ), -( mBeginHandleHeight + ry ) ), QPointF( 0, 0 ) );
1097  }
1098 
1099  setRect( 0, 0, fabs( mBeginHandleWidth + rx ), fabs( mBeginHandleHeight + ry ) );
1100 
1101  //show current size of selection in status bar
1102  mComposition->setStatusMessage( QString( tr( "width: %1 mm height: %2 mm" ) ).arg( rect().width() ).arg( rect().height() ) );
1103 }
1104 
1105 QPointF QgsComposerMouseHandles::snapPoint( QPointF point, QgsComposerMouseHandles::SnapGuideMode mode )
1106 {
1107  //snap to grid
1108  QPointF snappedPoint = mComposition->snapPointToGrid( point );
1109 
1110  if ( snappedPoint != point ) //don't do align snap if grid snap has been done
1111  {
1112  deleteAlignItems();
1113  return snappedPoint;
1114  }
1115 
1116  //align item
1117  if ( !mComposition->alignmentSnap() && !mComposition->smartGuidesEnabled() )
1118  {
1119  return point;
1120  }
1121 
1122  double alignX = 0;
1123  double alignY = 0;
1124 
1125  //depending on the mode, we either snap just the single point, or all the bounds of the selection
1126  switch ( mode )
1127  {
1129  snappedPoint = alignItem( alignX, alignY, point.x(), point.y() );
1130  break;
1132  snappedPoint = alignPos( point, alignX, alignY );
1133  break;
1134  }
1135 
1136  if ( !qgsDoubleNear( alignX, -1.0 ) )
1137  {
1138  QGraphicsLineItem* item = hAlignSnapItem();
1139  int numPages = mComposition->numPages();
1140  double yLineCoord = 300; //default in case there is no single page
1141  if ( numPages > 0 )
1142  {
1143  yLineCoord = mComposition->paperHeight() * numPages + mComposition->spaceBetweenPages() * ( numPages - 1 );
1144  }
1145  item->setLine( QLineF( alignX, 0, alignX, yLineCoord ) );
1146  item->show();
1147  }
1148  else
1149  {
1150  deleteHAlignSnapItem();
1151  }
1152  if ( !qgsDoubleNear( alignY, -1.0 ) )
1153  {
1154  QGraphicsLineItem* item = vAlignSnapItem();
1155  item->setLine( QLineF( 0, alignY, mComposition->paperWidth(), alignY ) );
1156  item->show();
1157  }
1158  else
1159  {
1160  deleteVAlignSnapItem();
1161  }
1162  return snappedPoint;
1163 }
1164 
1165 QGraphicsLineItem* QgsComposerMouseHandles::hAlignSnapItem()
1166 {
1167  if ( !mHAlignSnapItem )
1168  {
1169  mHAlignSnapItem = new QGraphicsLineItem( nullptr );
1170  mHAlignSnapItem->setPen( QPen( QColor( Qt::red ) ) );
1171  scene()->addItem( mHAlignSnapItem );
1172  mHAlignSnapItem->setZValue( 90 );
1173  }
1174  return mHAlignSnapItem;
1175 }
1176 
1177 QGraphicsLineItem* QgsComposerMouseHandles::vAlignSnapItem()
1178 {
1179  if ( !mVAlignSnapItem )
1180  {
1181  mVAlignSnapItem = new QGraphicsLineItem( nullptr );
1182  mVAlignSnapItem->setPen( QPen( QColor( Qt::red ) ) );
1183  scene()->addItem( mVAlignSnapItem );
1184  mVAlignSnapItem->setZValue( 90 );
1185  }
1186  return mVAlignSnapItem;
1187 }
1188 
1189 void QgsComposerMouseHandles::deleteHAlignSnapItem()
1190 {
1191  if ( mHAlignSnapItem )
1192  {
1193  scene()->removeItem( mHAlignSnapItem );
1194  delete mHAlignSnapItem;
1195  mHAlignSnapItem = nullptr;
1196  }
1197 }
1198 
1199 void QgsComposerMouseHandles::deleteVAlignSnapItem()
1200 {
1201  if ( mVAlignSnapItem )
1202  {
1203  scene()->removeItem( mVAlignSnapItem );
1204  delete mVAlignSnapItem;
1205  mVAlignSnapItem = nullptr;
1206  }
1207 }
1208 
1209 void QgsComposerMouseHandles::deleteAlignItems()
1210 {
1211  deleteHAlignSnapItem();
1212  deleteVAlignSnapItem();
1213 }
1214 
1215 QPointF QgsComposerMouseHandles::alignItem( double& alignX, double& alignY, double unalignedX, double unalignedY )
1216 {
1217  double left = unalignedX;
1218  double right = left + rect().width();
1219  double midH = ( left + right ) / 2.0;
1220  double top = unalignedY;
1221  double bottom = top + rect().height();
1222  double midV = ( top + bottom ) / 2.0;
1223 
1224  QMap<double, const QgsComposerItem* > xAlignCoordinates;
1225  QMap<double, const QgsComposerItem* > yAlignCoordinates;
1226  collectAlignCoordinates( xAlignCoordinates, yAlignCoordinates );
1227 
1228  //find nearest matches x
1229  double xItemLeft = left; //new left coordinate of the item
1230  double xAlignCoord = 0;
1231  double smallestDiffX = DBL_MAX;
1232 
1233  checkNearestItem( left, xAlignCoordinates, smallestDiffX, 0, xItemLeft, xAlignCoord );
1234  checkNearestItem( midH, xAlignCoordinates, smallestDiffX, ( left - right ) / 2.0, xItemLeft, xAlignCoord );
1235  checkNearestItem( right, xAlignCoordinates, smallestDiffX, left - right, xItemLeft, xAlignCoord );
1236 
1237  //find nearest matches y
1238  double yItemTop = top; //new top coordinate of the item
1239  double yAlignCoord = 0;
1240  double smallestDiffY = DBL_MAX;
1241 
1242  checkNearestItem( top, yAlignCoordinates, smallestDiffY, 0, yItemTop, yAlignCoord );
1243  checkNearestItem( midV, yAlignCoordinates, smallestDiffY, ( top - bottom ) / 2.0, yItemTop, yAlignCoord );
1244  checkNearestItem( bottom, yAlignCoordinates, smallestDiffY, top - bottom, yItemTop, yAlignCoord );
1245 
1246  double xCoord = ( smallestDiffX < 5 ) ? xItemLeft : unalignedX;
1247  alignX = ( smallestDiffX < 5 ) ? xAlignCoord : -1;
1248  double yCoord = ( smallestDiffY < 5 ) ? yItemTop : unalignedY;
1249  alignY = ( smallestDiffY < 5 ) ? yAlignCoord : -1;
1250  return QPointF( xCoord, yCoord );
1251 }
1252 
1253 QPointF QgsComposerMouseHandles::alignPos( QPointF pos, double& alignX, double& alignY )
1254 {
1255  QMap<double, const QgsComposerItem* > xAlignCoordinates;
1256  QMap<double, const QgsComposerItem* > yAlignCoordinates;
1257  collectAlignCoordinates( xAlignCoordinates, yAlignCoordinates );
1258 
1259  double nearestX = pos.x();
1260  double nearestY = pos.y();
1261  if ( !nearestItem( xAlignCoordinates, pos.x(), nearestX )
1262  || !nearestItem( yAlignCoordinates, pos.y(), nearestY ) )
1263  {
1264  alignX = -1;
1265  alignY = -1;
1266  return pos;
1267  }
1268 
1269  //convert snap tolerance from pixels to mm
1270  double viewScaleFactor = graphicsView()->transform().m11();
1271  double alignThreshold = mComposition->snapTolerance() / viewScaleFactor;
1272 
1273  QPointF result( pos.x(), pos.y() );
1274  if ( fabs( nearestX - pos.x() ) < alignThreshold )
1275  {
1276  result.setX( nearestX );
1277  alignX = nearestX;
1278  }
1279  else
1280  {
1281  alignX = -1;
1282  }
1283 
1284  if ( fabs( nearestY - pos.y() ) < alignThreshold )
1285  {
1286  result.setY( nearestY );
1287  alignY = nearestY;
1288  }
1289  else
1290  {
1291  alignY = -1;
1292  }
1293  return result;
1294 }
1295 
1296 void QgsComposerMouseHandles::collectAlignCoordinates( QMap< double, const QgsComposerItem* >& alignCoordsX, QMap< double, const QgsComposerItem* >& alignCoordsY )
1297 {
1298  alignCoordsX.clear();
1299  alignCoordsY.clear();
1300 
1301  if ( mComposition->smartGuidesEnabled() )
1302  {
1303  QList<QGraphicsItem *> itemList = mComposition->items();
1304  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
1305  for ( ; itemIt != itemList.end(); ++itemIt )
1306  {
1307  const QgsComposerItem* currentItem = dynamic_cast<const QgsComposerItem *>( *itemIt );
1308  //don't snap to selected items, since they're the ones that will be snapping to something else
1309  //also ignore group members - only snap to bounds of group itself
1310  //also ignore hidden items
1311  if ( !currentItem || currentItem->selected() || currentItem->isGroupMember() || !currentItem->isVisible() )
1312  {
1313  continue;
1314  }
1315  QRectF itemRect;
1316  if ( dynamic_cast<const QgsPaperItem *>( *itemIt ) )
1317  {
1318  //if snapping to paper use the paper item's rect rather then the bounding rect,
1319  //since we want to snap to the page edge and not any outlines drawn around the page
1320  itemRect = currentItem->mapRectToScene( currentItem->rect() );
1321  }
1322  else
1323  {
1324  itemRect = currentItem->mapRectToScene( currentItem->rectWithFrame() );
1325  }
1326  alignCoordsX.insert( itemRect.left(), currentItem );
1327  alignCoordsX.insert( itemRect.right(), currentItem );
1328  alignCoordsX.insert( itemRect.center().x(), currentItem );
1329  alignCoordsY.insert( itemRect.top(), currentItem );
1330  alignCoordsY.insert( itemRect.center().y(), currentItem );
1331  alignCoordsY.insert( itemRect.bottom(), currentItem );
1332  }
1333  }
1334 
1335  //arbitrary snap lines
1336  if ( mComposition->alignmentSnap() )
1337  {
1339  for ( ; sIt != mComposition->snapLines()->constEnd(); ++sIt )
1340  {
1341  double x = ( *sIt )->line().x1();
1342  double y = ( *sIt )->line().y1();
1343  if ( qgsDoubleNear( y, 0.0 ) )
1344  {
1345  alignCoordsX.insert( x, nullptr );
1346  }
1347  else
1348  {
1349  alignCoordsY.insert( y, nullptr );
1350  }
1351  }
1352  }
1353 }
1354 
1355 void QgsComposerMouseHandles::checkNearestItem( double checkCoord, const QMap< double, const QgsComposerItem* >& alignCoords, double& smallestDiff, double itemCoordOffset, double& itemCoord, double& alignCoord )
1356 {
1357  double currentCoord = 0;
1358  if ( !nearestItem( alignCoords, checkCoord, currentCoord ) )
1359  {
1360  return;
1361  }
1362 
1363  double currentDiff = fabs( checkCoord - currentCoord );
1364  //convert snap tolerance from pixels to mm
1365  double viewScaleFactor = graphicsView()->transform().m11();
1366  double alignThreshold = mComposition->snapTolerance() / viewScaleFactor;
1367 
1368  if ( currentDiff < alignThreshold && currentDiff < smallestDiff )
1369  {
1370  itemCoord = currentCoord + itemCoordOffset;
1371  alignCoord = currentCoord;
1372  smallestDiff = currentDiff;
1373  }
1374 }
1375 
1376 bool QgsComposerMouseHandles::nearestItem( const QMap< double, const QgsComposerItem* >& coords, double value, double& nearestValue ) const
1377 {
1378  if ( coords.size() < 1 )
1379  {
1380  return false;
1381  }
1382 
1384  if ( it == coords.constBegin() ) //value smaller than first map value
1385  {
1386  nearestValue = it.key();
1387  return true;
1388  }
1389  else if ( it == coords.constEnd() ) //value larger than last map value
1390  {
1391  --it;
1392  nearestValue = it.key();
1393  return true;
1394  }
1395  else
1396  {
1397  //get smaller value and larger value and return the closer one
1398  double upperVal = it.key();
1399  --it;
1400  double lowerVal = it.key();
1401 
1402  double lowerDiff = value - lowerVal;
1403  double upperDiff = upperVal - value;
1404  if ( lowerDiff < upperDiff )
1405  {
1406  nearestValue = lowerVal;
1407  return true;
1408  }
1409  else
1410  {
1411  nearestValue = upperVal;
1412  return true;
1413  }
1414  }
1415 }
1416 
QgsComposerMouseHandles::MouseAction mouseActionForScenePos(QPointF sceneCoordPos)
Finds out which mouse move action to choose depending on the scene cursor position.
QRectF mapRectFromItem(const QGraphicsItem *item, const QRectF &rect) const
static void relativeResizeRect(QRectF &rectToResize, const QRectF &boundsBefore, const QRectF &boundsAfter)
Resizes a QRectF relative to a resized bounding rectangle.
void setDirty(bool b=true)
Flag the project as dirty (modified).
Definition: qgsproject.cpp:410
QUndoStack * undoStack()
Returns pointer to undo/redo command storage.
qreal x() const
qreal y() const
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override
void setStyle(Qt::PenStyle style)
bool isGroupMember() const
Returns whether this item is part of a group.
void setCursor(const QCursor &)
void setCompositionMode(CompositionMode mode)
void paint(QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget) override
void selectedItemRotationChanged()
Redraws handles when selected item rotation changes.
QList< QGraphicsItem * > items() const
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override
bool alignmentSnap() const
void setAcceptHoverEvents(bool enabled)
const_iterator constBegin() const
qreal rotation() const
const T & at(int i) const
A item that forms part of a map composition.
void save()
int numPages() const
Returns the number of pages in the composition.
QWidget * viewport() const
qreal top() const
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override
void savePreviousState()
Saves current item state as previous state.
MouseAction
Describes the action (move or resize in different directon) to be done during mouse move...
void selectedItemSizeChanged()
Redraws handles when selected item size changes.
QRectF mapRectToScene(const QRectF &rect) const
void clear()
QGraphicsScene * scene() const
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
void setStatusMessage(const QString &message)
Sets the status bar message for the composer window.
QString tr(const char *sourceText, const char *disambiguation, int n)
qreal left() const
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Compare two doubles (but allow some difference)
Definition: qgis.h:353
void update(const QRectF &rect)
bool preventCursorChange() const
int size() const
void setRect(const QRectF &rectangle)
QgsComposition::PlotStyle plotStyle() const
virtual bool event(QEvent *e)
qreal bottom() const
void drawRect(const QRectF &rectangle)
virtual QRectF boundingRect() const
QTransform transform() const
QPointF pos() const
QTransform & translate(qreal dx, qreal dy)
qreal x() const
qreal y() const
QPointF p2() const
void addPolygon(const QPolygonF &polygon)
const Key & key() const
void removeItem(QGraphicsItem *item)
QCursor cursor() const
void setPen(const QColor &color)
virtual bool selected() const
Is selected.
void setPos(const QPointF &pos)
QList< QGraphicsView * > views() const
QRectF normalized() const
qreal m11() const
void setAngle(qreal angle)
bool boundingBoxesVisible() const
Returns whether selection bounding boxes should be shown in the composition.
bool isEmpty() const
QPointF topLeft() const
const_iterator constEnd() const
void setLine(const QLineF &line)
QPointF mapFromScene(const QPointF &point) const
QTransform itemTransform(const QGraphicsItem *other, bool *ok) const
void translate(qreal dx, qreal dy)
void setBrush(const QBrush &brush)
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override
QPointF center() const
QRectF united(const QRectF &rectangle) const
iterator lowerBound(const Key &key)
void selectionChanged()
Sets up listeners to sizeChanged signal for all selected items.
Graphics scene for map printing.
void setPen(const QPen &pen)
iterator end()
qreal right() const
bool smartGuidesEnabled() const
void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override
void saveAfterState()
Saves current item state as after state.
void restore()
bool isVisible() const
QPointF mapToScene(const QPointF &point) const
qreal width() const
QPointF mapFromItem(const QGraphicsItem *item, const QPointF &point) const
Undo command to undo/redo all composer item related changes.
void drawPath(const QPainterPath &path)
void setWidth(int width)
void resetTransform()
QTransform transform() const
QPointF snapPointToGrid(QPointF scenePoint) const
Snaps a scene coordinate point to grid.
int snapTolerance() const
Returns the snap tolerance to use when automatically snapping items during movement and resizing to g...
void setX(qreal x)
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:382
void setTransform(const QTransform &matrix, bool combine)
qreal angle() const
double paperHeight() const
Height of paper item.
void setRotation(qreal angle)
qreal height() const
double paperWidth() const
Width of paper item.
iterator insert(const Key &key, const T &value)
double ANALYSIS_EXPORT min(double x, double y)
Returns the minimum of two doubles or the first argument if both are equal.
const_iterator constEnd() const
const_iterator constBegin() const
QgsComposerMouseHandles(QgsComposition *composition)
void addItem(QGraphicsItem *item)
qreal height() const
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void mousePressEvent(QGraphicsSceneMouseEvent *event) override
void setZValue(qreal z)
iterator begin()
int size() const
QPointF scenePos() const
qreal width() const
double spaceBetweenPages() const
Returns the vertical space between pages in a composer view.
void push(QUndoCommand *cmd)
virtual QRectF rectWithFrame() const
Returns the item&#39;s rectangular bounds, including any bleed caused by the item&#39;s frame.
QPointF lastScenePos() const
QRectF rect() const
QList< QgsComposerItem * > selectedComposerItems(const bool includeLockedItems=true)
Returns list of selected composer items.
QList< QGraphicsLineItem *> * snapLines()
Returns pointer to snap lines collection.