QGIS API Documentation  2.99.0-Master (ba079d8)
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  connect( mComposition, &QGraphicsScene::selectionChanged, this, &QgsComposerMouseHandles::selectionChanged );
50 
51  //accept hover events, required for changing cursor to resize cursors
52  setAcceptHoverEvents( true );
53 }
54 
55 QGraphicsView *QgsComposerMouseHandles::graphicsView()
56 {
57  //have we already found the current view?
58  if ( mGraphicsView )
59  {
60  return mGraphicsView;
61  }
62 
63  //otherwise, try and find current view attached to composition
64  if ( scene() )
65  {
66  QList<QGraphicsView *> viewList = scene()->views();
67  if ( !viewList.isEmpty() )
68  {
69  mGraphicsView = viewList.at( 0 );
70  return mGraphicsView;
71  }
72  }
73 
74  //no view attached to composition
75  return nullptr;
76 }
77 
78 void QgsComposerMouseHandles::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget )
79 {
80  Q_UNUSED( itemStyle );
81  Q_UNUSED( pWidget );
82 
83  if ( mComposition->plotStyle() != QgsComposition::Preview )
84  {
85  //don't draw selection handles in composition outputs
86  return;
87  }
88 
89  if ( mComposition->boundingBoxesVisible() )
90  {
91  //draw resize handles around bounds of entire selection
92  double rectHandlerSize = rectHandlerBorderTolerance();
93  drawHandles( painter, rectHandlerSize );
94  }
95 
96  if ( mIsResizing || mIsDragging || mComposition->boundingBoxesVisible() )
97  {
98  //draw dotted boxes around selected items
99  drawSelectedItemBounds( painter );
100  }
101 }
102 
103 void QgsComposerMouseHandles::drawHandles( QPainter *painter, double rectHandlerSize )
104 {
105  //blue, zero width cosmetic pen for outline
106  QPen handlePen = QPen( QColor( 55, 140, 195, 255 ) );
107  handlePen.setWidth( 0 );
108  painter->setPen( handlePen );
109 
110  //draw box around entire selection bounds
111  painter->setBrush( Qt::NoBrush );
112  painter->drawRect( QRectF( 0, 0, rect().width(), rect().height() ) );
113 
114  //draw resize handles, using a filled white box
115  painter->setBrush( QColor( 255, 255, 255, 255 ) );
116  //top left
117  painter->drawRect( QRectF( 0, 0, rectHandlerSize, rectHandlerSize ) );
118  //mid top
119  painter->drawRect( QRectF( ( rect().width() - rectHandlerSize ) / 2, 0, rectHandlerSize, rectHandlerSize ) );
120  //top right
121  painter->drawRect( QRectF( rect().width() - rectHandlerSize, 0, rectHandlerSize, rectHandlerSize ) );
122  //mid left
123  painter->drawRect( QRectF( 0, ( rect().height() - rectHandlerSize ) / 2, rectHandlerSize, rectHandlerSize ) );
124  //mid right
125  painter->drawRect( QRectF( rect().width() - rectHandlerSize, ( rect().height() - rectHandlerSize ) / 2, rectHandlerSize, rectHandlerSize ) );
126  //bottom left
127  painter->drawRect( QRectF( 0, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) );
128  //mid bottom
129  painter->drawRect( QRectF( ( rect().width() - rectHandlerSize ) / 2, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) );
130  //bottom right
131  painter->drawRect( QRectF( rect().width() - rectHandlerSize, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) );
132 }
133 
134 void QgsComposerMouseHandles::drawSelectedItemBounds( QPainter *painter )
135 {
136  //draw dotted border around selected items to give visual feedback which items are selected
137  QList<QgsComposerItem *> selectedItems = mComposition->selectedComposerItems( false );
138  if ( selectedItems.isEmpty() )
139  {
140  return;
141  }
142 
143  //use difference mode so that they are visible regardless of item colors
144  painter->save();
145  painter->setCompositionMode( QPainter::CompositionMode_Difference );
146 
147  // use a grey dashed pen - in difference mode this should always be visible
148  QPen selectedItemPen = QPen( QColor( 144, 144, 144, 255 ) );
149  selectedItemPen.setStyle( Qt::DashLine );
150  selectedItemPen.setWidth( 0 );
151  painter->setPen( selectedItemPen );
152  painter->setBrush( Qt::NoBrush );
153 
154  QList<QgsComposerItem *>::iterator itemIter = selectedItems.begin();
155  for ( ; itemIter != selectedItems.end(); ++itemIter )
156  {
157  //get bounds of selected item
158  QPolygonF itemBounds;
159  if ( mIsDragging && !( *itemIter )->positionLock() )
160  {
161  //if currently dragging, draw selected item bounds relative to current mouse position
162  //first, get bounds of current item in scene coordinates
163  QPolygonF itemSceneBounds = ( *itemIter )->mapToScene( ( *itemIter )->rectWithFrame() );
164  //now, translate it by the current movement amount
165  //IMPORTANT - this is done in scene coordinates, since we don't want any rotation/non-translation transforms to affect the movement
166  itemSceneBounds.translate( transform().dx(), transform().dy() );
167  //finally, remap it to the mouse handle item's coordinate system so it's ready for drawing
168  itemBounds = mapFromScene( itemSceneBounds );
169  }
170  else if ( mIsResizing && !( *itemIter )->positionLock() )
171  {
172  //if currently resizing, calculate relative resize of this item
173  if ( selectedItems.size() > 1 )
174  {
175  //get item bounds in mouse handle item's coordinate system
176  QRectF itemRect = mapRectFromItem( ( *itemIter ), ( *itemIter )->rectWithFrame() );
177  //now, resize it relative to the current resized dimensions of the mouse handles
178  QgsComposerUtils::relativeResizeRect( itemRect, QRectF( -mResizeMoveX, -mResizeMoveY, mBeginHandleWidth, mBeginHandleHeight ), mResizeRect );
179  itemBounds = QPolygonF( itemRect );
180  }
181  else
182  {
183  //single item selected
184  itemBounds = rect();
185  }
186  }
187  else
188  {
189  //not resizing or moving, so just map from scene bounds
190  itemBounds = mapRectFromItem( ( *itemIter ), ( *itemIter )->rectWithFrame() );
191  }
192 
193  // drawPolygon causes issues on windows - corners of path may be missing resulting in triangles being drawn
194  // instead of rectangles! (Same cause as #13343)
195  QPainterPath path;
196  path.addPolygon( itemBounds );
197  painter->drawPath( path );
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  {
218  }
219  else
220  {
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.isEmpty() )
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 ( !qgsDoubleNear( ( *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( 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( 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.isEmpty() )
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, QLatin1String( "" ), 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  mComposition->project()->setDirty( 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 deselectable items (e.g., items which make up an item group)
642  continue;
643  }
644  QgsComposerItemCommand *subcommand = new QgsComposerItemCommand( *itemIter, QLatin1String( "" ), 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  mComposition->project()->setDirty( 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( 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( 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 && qgsDoubleNear( rotation(), 0.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( 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 ( qgsDoubleNear( rotation(), 0.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 && !qgsDoubleNear( mBeginHandleHeight, 0.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;
892  my = diffY;
893  rx = diffX;
894  ry = -diffY;
895  }
896  else
897  {
898  mx = 0;
899  my = diffY;
900  rx = 0;
901  ry = -diffY;
902  }
903  break;
904  }
905 
907  {
908  if ( ratio )
909  {
910  diffX = ( ( mBeginHandleHeight + diffY ) * ratio ) - mBeginHandleWidth;
911  mx = -diffX / 2;
912  my = 0;
913  rx = diffX;
914  ry = diffY;
915  }
916  else
917  {
918  mx = 0;
919  my = 0;
920  rx = 0;
921  ry = diffY;
922  }
923  break;
924  }
925 
926  //horizontal resize
928  {
929  if ( ratio )
930  {
931  diffY = ( ( mBeginHandleWidth - diffX ) / ratio ) - mBeginHandleHeight;
932  mx = diffX;
933  my = -diffY / 2;
934  rx = -diffX;
935  ry = diffY;
936  }
937  else
938  {
939  mx = diffX, my = 0;
940  rx = -diffX;
941  ry = 0;
942  }
943  break;
944  }
945 
947  {
948  if ( ratio )
949  {
950  diffY = ( ( mBeginHandleWidth + diffX ) / ratio ) - mBeginHandleHeight;
951  mx = 0;
952  my = -diffY / 2;
953  rx = diffX;
954  ry = diffY;
955  }
956  else
957  {
958  mx = 0;
959  my = 0;
960  rx = diffX, ry = 0;
961  }
962  break;
963  }
964 
965  //diagonal resize
967  {
968  if ( ratio )
969  {
970  //ratio locked resize
971  if ( ( mBeginHandleWidth - diffX ) / ( mBeginHandleHeight - diffY ) > ratio )
972  {
973  diffX = mBeginHandleWidth - ( ( mBeginHandleHeight - diffY ) * ratio );
974  }
975  else
976  {
977  diffY = mBeginHandleHeight - ( ( mBeginHandleWidth - diffX ) / ratio );
978  }
979  }
980  mx = diffX, my = diffY;
981  rx = -diffX;
982  ry = -diffY;
983  break;
984  }
985 
987  {
988  if ( ratio )
989  {
990  //ratio locked resize
991  if ( ( mBeginHandleWidth + diffX ) / ( mBeginHandleHeight + diffY ) > ratio )
992  {
993  diffX = ( ( mBeginHandleHeight + diffY ) * ratio ) - mBeginHandleWidth;
994  }
995  else
996  {
997  diffY = ( ( mBeginHandleWidth + diffX ) / ratio ) - mBeginHandleHeight;
998  }
999  }
1000  mx = 0;
1001  my = 0;
1002  rx = diffX, ry = diffY;
1003  break;
1004  }
1005 
1007  {
1008  if ( ratio )
1009  {
1010  //ratio locked resize
1011  if ( ( mBeginHandleWidth + diffX ) / ( mBeginHandleHeight - diffY ) > ratio )
1012  {
1013  diffX = ( ( mBeginHandleHeight - diffY ) * ratio ) - mBeginHandleWidth;
1014  }
1015  else
1016  {
1017  diffY = mBeginHandleHeight - ( ( mBeginHandleWidth + diffX ) / ratio );
1018  }
1019  }
1020  mx = 0;
1021  my = diffY, rx = diffX, ry = -diffY;
1022  break;
1023  }
1024 
1026  {
1027  if ( ratio )
1028  {
1029  //ratio locked resize
1030  if ( ( mBeginHandleWidth - diffX ) / ( mBeginHandleHeight + diffY ) > ratio )
1031  {
1032  diffX = mBeginHandleWidth - ( ( mBeginHandleHeight + diffY ) * ratio );
1033  }
1034  else
1035  {
1036  diffY = ( ( mBeginHandleWidth - diffX ) / ratio ) - mBeginHandleHeight;
1037  }
1038  }
1039  mx = diffX, my = 0;
1040  rx = -diffX;
1041  ry = diffY;
1042  break;
1043  }
1044 
1048  break;
1049  }
1050 
1051  //resizing from center of objects?
1052  if ( fromCenter )
1053  {
1054  my = -ry;
1055  mx = -rx;
1056  ry = 2 * ry;
1057  rx = 2 * rx;
1058  }
1059 
1060  //update selection handle rectangle
1061 
1062  //make sure selection handle size rectangle is normalized (ie, left coord < right coord)
1063  mResizeMoveX = mBeginHandleWidth + rx > 0 ? mx : mx + mBeginHandleWidth + rx;
1064  mResizeMoveY = mBeginHandleHeight + ry > 0 ? my : my + mBeginHandleHeight + ry;
1065 
1066  //calculate movement in scene coordinates
1067  QLineF translateLine = QLineF( 0, 0, mResizeMoveX, mResizeMoveY );
1068  translateLine.setAngle( translateLine.angle() - rotation() );
1069  QPointF sceneTranslate = translateLine.p2();
1070 
1071  //move selection handles
1072  QTransform itemTransform;
1073  itemTransform.translate( sceneTranslate.x(), sceneTranslate.y() );
1074  setTransform( itemTransform );
1075 
1076  //handle non-normalised resizes - e.g., dragging the left handle so far to the right that it's past the right handle
1077  if ( mBeginHandleWidth + rx >= 0 && mBeginHandleHeight + ry >= 0 )
1078  {
1079  mResizeRect = QRectF( 0, 0, mBeginHandleWidth + rx, mBeginHandleHeight + ry );
1080  }
1081  else if ( mBeginHandleHeight + ry >= 0 )
1082  {
1083  mResizeRect = QRectF( QPointF( -( mBeginHandleWidth + rx ), 0 ), QPointF( 0, mBeginHandleHeight + ry ) );
1084  }
1085  else if ( mBeginHandleWidth + rx >= 0 )
1086  {
1087  mResizeRect = QRectF( QPointF( 0, -( mBeginHandleHeight + ry ) ), QPointF( mBeginHandleWidth + rx, 0 ) );
1088  }
1089  else
1090  {
1091  mResizeRect = QRectF( QPointF( -( mBeginHandleWidth + rx ), -( mBeginHandleHeight + ry ) ), QPointF( 0, 0 ) );
1092  }
1093 
1094  setRect( 0, 0, fabs( mBeginHandleWidth + rx ), fabs( mBeginHandleHeight + ry ) );
1095 
1096  //show current size of selection in status bar
1097  mComposition->setStatusMessage( QString( tr( "width: %1 mm height: %2 mm" ) ).arg( rect().width() ).arg( rect().height() ) );
1098 }
1099 
1100 QPointF QgsComposerMouseHandles::snapPoint( QPointF point, QgsComposerMouseHandles::SnapGuideMode mode )
1101 {
1102  //snap to grid
1103  QPointF snappedPoint = mComposition->snapPointToGrid( point );
1104 
1105  if ( snappedPoint != point ) //don't do align snap if grid snap has been done
1106  {
1107  deleteAlignItems();
1108  return snappedPoint;
1109  }
1110 
1111  //align item
1112  if ( !mComposition->alignmentSnap() && !mComposition->smartGuidesEnabled() )
1113  {
1114  return point;
1115  }
1116 
1117  double alignX = 0;
1118  double alignY = 0;
1119 
1120  //depending on the mode, we either snap just the single point, or all the bounds of the selection
1121  switch ( mode )
1122  {
1124  snappedPoint = alignItem( alignX, alignY, point.x(), point.y() );
1125  break;
1127  snappedPoint = alignPos( point, alignX, alignY );
1128  break;
1129  }
1130 
1131  if ( !qgsDoubleNear( alignX, -1.0 ) )
1132  {
1133  QGraphicsLineItem *item = hAlignSnapItem();
1134  int numPages = mComposition->numPages();
1135  double yLineCoord = 300; //default in case there is no single page
1136  if ( numPages > 0 )
1137  {
1138  yLineCoord = mComposition->paperHeight() * numPages + mComposition->spaceBetweenPages() * ( numPages - 1 );
1139  }
1140  item->setLine( QLineF( alignX, 0, alignX, yLineCoord ) );
1141  item->show();
1142  }
1143  else
1144  {
1145  deleteHAlignSnapItem();
1146  }
1147  if ( !qgsDoubleNear( alignY, -1.0 ) )
1148  {
1149  QGraphicsLineItem *item = vAlignSnapItem();
1150  item->setLine( QLineF( 0, alignY, mComposition->paperWidth(), alignY ) );
1151  item->show();
1152  }
1153  else
1154  {
1155  deleteVAlignSnapItem();
1156  }
1157  return snappedPoint;
1158 }
1159 
1160 QGraphicsLineItem *QgsComposerMouseHandles::hAlignSnapItem()
1161 {
1162  if ( !mHAlignSnapItem )
1163  {
1164  mHAlignSnapItem = new QGraphicsLineItem( nullptr );
1165  QPen pen = QPen( QColor( Qt::red ) );
1166  pen.setWidthF( 0.0 );
1167  mHAlignSnapItem->setPen( pen );
1168  scene()->addItem( mHAlignSnapItem );
1169  mHAlignSnapItem->setZValue( 90 );
1170  }
1171  return mHAlignSnapItem;
1172 }
1173 
1174 QGraphicsLineItem *QgsComposerMouseHandles::vAlignSnapItem()
1175 {
1176  if ( !mVAlignSnapItem )
1177  {
1178  mVAlignSnapItem = new QGraphicsLineItem( nullptr );
1179  QPen pen = QPen( QColor( Qt::red ) );
1180  pen.setWidthF( 0.0 );
1181  mVAlignSnapItem->setPen( pen );
1182  scene()->addItem( mVAlignSnapItem );
1183  mVAlignSnapItem->setZValue( 90 );
1184  }
1185  return mVAlignSnapItem;
1186 }
1187 
1188 void QgsComposerMouseHandles::deleteHAlignSnapItem()
1189 {
1190  if ( mHAlignSnapItem )
1191  {
1192  scene()->removeItem( mHAlignSnapItem );
1193  delete mHAlignSnapItem;
1194  mHAlignSnapItem = nullptr;
1195  }
1196 }
1197 
1198 void QgsComposerMouseHandles::deleteVAlignSnapItem()
1199 {
1200  if ( mVAlignSnapItem )
1201  {
1202  scene()->removeItem( mVAlignSnapItem );
1203  delete mVAlignSnapItem;
1204  mVAlignSnapItem = nullptr;
1205  }
1206 }
1207 
1208 void QgsComposerMouseHandles::deleteAlignItems()
1209 {
1210  deleteHAlignSnapItem();
1211  deleteVAlignSnapItem();
1212 }
1213 
1214 QPointF QgsComposerMouseHandles::alignItem( double &alignX, double &alignY, double unalignedX, double unalignedY )
1215 {
1216  double left = unalignedX;
1217  double right = left + rect().width();
1218  double midH = ( left + right ) / 2.0;
1219  double top = unalignedY;
1220  double bottom = top + rect().height();
1221  double midV = ( top + bottom ) / 2.0;
1222 
1223  QMap<double, const QgsComposerItem * > xAlignCoordinates;
1224  QMap<double, const QgsComposerItem * > yAlignCoordinates;
1225  collectAlignCoordinates( xAlignCoordinates, yAlignCoordinates );
1226 
1227  //find nearest matches x
1228  double xItemLeft = left; //new left coordinate of the item
1229  double xAlignCoord = 0;
1230  double smallestDiffX = DBL_MAX;
1231 
1232  checkNearestItem( left, xAlignCoordinates, smallestDiffX, 0, xItemLeft, xAlignCoord );
1233  checkNearestItem( midH, xAlignCoordinates, smallestDiffX, ( left - right ) / 2.0, xItemLeft, xAlignCoord );
1234  checkNearestItem( right, xAlignCoordinates, smallestDiffX, left - right, xItemLeft, xAlignCoord );
1235 
1236  //find nearest matches y
1237  double yItemTop = top; //new top coordinate of the item
1238  double yAlignCoord = 0;
1239  double smallestDiffY = DBL_MAX;
1240 
1241  checkNearestItem( top, yAlignCoordinates, smallestDiffY, 0, yItemTop, yAlignCoord );
1242  checkNearestItem( midV, yAlignCoordinates, smallestDiffY, ( top - bottom ) / 2.0, yItemTop, yAlignCoord );
1243  checkNearestItem( bottom, yAlignCoordinates, smallestDiffY, top - bottom, yItemTop, yAlignCoord );
1244 
1245  double xCoord = ( smallestDiffX < 5 ) ? xItemLeft : unalignedX;
1246  alignX = ( smallestDiffX < 5 ) ? xAlignCoord : -1;
1247  double yCoord = ( smallestDiffY < 5 ) ? yItemTop : unalignedY;
1248  alignY = ( smallestDiffY < 5 ) ? yAlignCoord : -1;
1249  return QPointF( xCoord, yCoord );
1250 }
1251 
1252 QPointF QgsComposerMouseHandles::alignPos( QPointF pos, double &alignX, double &alignY )
1253 {
1254  QMap<double, const QgsComposerItem * > xAlignCoordinates;
1255  QMap<double, const QgsComposerItem * > yAlignCoordinates;
1256  collectAlignCoordinates( xAlignCoordinates, yAlignCoordinates );
1257 
1258  double nearestX = pos.x();
1259  double nearestY = pos.y();
1260  if ( !nearestItem( xAlignCoordinates, pos.x(), nearestX )
1261  || !nearestItem( yAlignCoordinates, pos.y(), nearestY ) )
1262  {
1263  alignX = -1;
1264  alignY = -1;
1265  return pos;
1266  }
1267 
1268  //convert snap tolerance from pixels to mm
1269  double viewScaleFactor = graphicsView()->transform().m11();
1270  double alignThreshold = mComposition->snapTolerance() / viewScaleFactor;
1271 
1272  QPointF result( pos.x(), pos.y() );
1273  if ( fabs( nearestX - pos.x() ) < alignThreshold )
1274  {
1275  result.setX( nearestX );
1276  alignX = nearestX;
1277  }
1278  else
1279  {
1280  alignX = -1;
1281  }
1282 
1283  if ( fabs( nearestY - pos.y() ) < alignThreshold )
1284  {
1285  result.setY( nearestY );
1286  alignY = nearestY;
1287  }
1288  else
1289  {
1290  alignY = -1;
1291  }
1292  return result;
1293 }
1294 
1295 void QgsComposerMouseHandles::collectAlignCoordinates( QMap< double, const QgsComposerItem * > &alignCoordsX, QMap< double, const QgsComposerItem * > &alignCoordsY )
1296 {
1297  alignCoordsX.clear();
1298  alignCoordsY.clear();
1299 
1300  if ( mComposition->smartGuidesEnabled() )
1301  {
1302  QList<QGraphicsItem *> itemList = mComposition->items();
1303  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
1304  for ( ; itemIt != itemList.end(); ++itemIt )
1305  {
1306  const QgsComposerItem *currentItem = dynamic_cast<const QgsComposerItem *>( *itemIt );
1307  //don't snap to selected items, since they're the ones that will be snapping to something else
1308  //also ignore group members - only snap to bounds of group itself
1309  //also ignore hidden items
1310  if ( !currentItem || currentItem->selected() || currentItem->isGroupMember() || !currentItem->isVisible() )
1311  {
1312  continue;
1313  }
1314  QRectF itemRect;
1315  if ( dynamic_cast<const QgsPaperItem *>( *itemIt ) )
1316  {
1317  //if snapping to paper use the paper item's rect rather then the bounding rect,
1318  //since we want to snap to the page edge and not any outlines drawn around the page
1319  itemRect = currentItem->mapRectToScene( currentItem->rect() );
1320  }
1321  else
1322  {
1323  itemRect = currentItem->mapRectToScene( currentItem->rectWithFrame() );
1324  }
1325  alignCoordsX.insert( itemRect.left(), currentItem );
1326  alignCoordsX.insert( itemRect.right(), currentItem );
1327  alignCoordsX.insert( itemRect.center().x(), currentItem );
1328  alignCoordsY.insert( itemRect.top(), currentItem );
1329  alignCoordsY.insert( itemRect.center().y(), currentItem );
1330  alignCoordsY.insert( itemRect.bottom(), currentItem );
1331  }
1332  }
1333 
1334  //arbitrary snap lines
1335  if ( mComposition->alignmentSnap() )
1336  {
1337  QList< QGraphicsLineItem * >::const_iterator sIt = mComposition->snapLines()->constBegin();
1338  for ( ; sIt != mComposition->snapLines()->constEnd(); ++sIt )
1339  {
1340  double x = ( *sIt )->line().x1();
1341  double y = ( *sIt )->line().y1();
1342  if ( qgsDoubleNear( y, 0.0 ) )
1343  {
1344  alignCoordsX.insert( x, nullptr );
1345  }
1346  else
1347  {
1348  alignCoordsY.insert( y, nullptr );
1349  }
1350  }
1351  }
1352 }
1353 
1354 void QgsComposerMouseHandles::checkNearestItem( double checkCoord, const QMap< double, const QgsComposerItem * > &alignCoords, double &smallestDiff, double itemCoordOffset, double &itemCoord, double &alignCoord )
1355 {
1356  double currentCoord = 0;
1357  if ( !nearestItem( alignCoords, checkCoord, currentCoord ) )
1358  {
1359  return;
1360  }
1361 
1362  double currentDiff = fabs( checkCoord - currentCoord );
1363  //convert snap tolerance from pixels to mm
1364  double viewScaleFactor = graphicsView()->transform().m11();
1365  double alignThreshold = mComposition->snapTolerance() / viewScaleFactor;
1366 
1367  if ( currentDiff < alignThreshold && currentDiff < smallestDiff )
1368  {
1369  itemCoord = currentCoord + itemCoordOffset;
1370  alignCoord = currentCoord;
1371  smallestDiff = currentDiff;
1372  }
1373 }
1374 
1375 bool QgsComposerMouseHandles::nearestItem( const QMap< double, const QgsComposerItem * > &coords, double value, double &nearestValue ) const
1376 {
1377  if ( coords.size() < 1 )
1378  {
1379  return false;
1380  }
1381 
1382  QMap< double, const QgsComposerItem * >::const_iterator it = coords.lowerBound( value );
1383  if ( it == coords.constBegin() ) //value smaller than first map value
1384  {
1385  nearestValue = it.key();
1386  return true;
1387  }
1388  else if ( it == coords.constEnd() ) //value larger than last map value
1389  {
1390  --it;
1391  nearestValue = it.key();
1392  return true;
1393  }
1394  else
1395  {
1396  //get smaller value and larger value and return the closer one
1397  double upperVal = it.key();
1398  --it;
1399  double lowerVal = it.key();
1400 
1401  double lowerDiff = value - lowerVal;
1402  double upperDiff = upperVal - value;
1403  if ( lowerDiff < upperDiff )
1404  {
1405  nearestValue = lowerVal;
1406  return true;
1407  }
1408  else
1409  {
1410  nearestValue = upperVal;
1411  return true;
1412  }
1413  }
1414 }
1415 
QgsComposerMouseHandles::MouseAction mouseActionForScenePos(QPointF sceneCoordPos)
Finds out which mouse move action to choose depending on the scene cursor position.
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:408
QUndoStack * undoStack()
Returns pointer to undo/redo command storage.
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override
bool isGroupMember() const
Returns whether this item is part of a group.
void itemRotationChanged(double newRotation)
Is emitted on item rotation change.
void paint(QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget) override
void selectedItemRotationChanged()
Redraws handles when selected item rotation changes.
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override
bool alignmentSnap() const
A item that forms part of a map composition.
int numPages() const
Returns the number of pages in the composition.
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.
void setStatusMessage(const QString &message)
Sets the status bar message for the composer window.
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Compare two doubles (but allow some difference)
Definition: qgis.h:193
bool preventCursorChange() const
QgsComposition::PlotStyle plotStyle() const
void frameChanged()
Emitted if the item&#39;s frame style changes.
virtual bool selected() const
Is selected.
bool boundingBoxesVisible() const
Returns whether selection bounding boxes should be shown in the composition.
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override
double ANALYSIS_EXPORT min(double x, double y)
Returns the minimum of two doubles or the first argument if both are equal.
Definition: MathUtils.cc:452
void selectionChanged()
Sets up listeners to sizeChanged signal for all selected items.
Graphics scene for map printing.
bool smartGuidesEnabled() const
void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override
void saveAfterState()
Saves current item state as after state.
Undo command to undo/redo all composer item related changes.
QgsProject * project() const
The project associated with the composition.
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 sizeChanged()
Emitted if the rectangle changes.
QList< QGraphicsLineItem *> * snapLines()
Returns pointer to snap lines collection.
double paperHeight() const
Height of paper item.
double paperWidth() const
Width of paper item.
QgsComposerMouseHandles(QgsComposition *composition)
void lockChanged()
Emitted if the item&#39;s lock status changes.
void mousePressEvent(QGraphicsSceneMouseEvent *event) override
double spaceBetweenPages() const
Returns the vertical space between pages in a composer view.
virtual QRectF rectWithFrame() const
Returns the item&#39;s rectangular bounds, including any bleed caused by the item&#39;s frame.
QList< QgsComposerItem * > selectedComposerItems(const bool includeLockedItems=true)
Returns list of selected composer items.