QGIS API Documentation  2.99.0-Master (37c43df)
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 
552 void QgsComposerMouseHandles::hoverMoveEvent( QGraphicsSceneHoverEvent * event )
553 {
554  setViewportCursor( cursorForPosition( event->pos() ) );
555 }
556 
557 void QgsComposerMouseHandles::hoverLeaveEvent( QGraphicsSceneHoverEvent * event )
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 
574 void QgsComposerMouseHandles::mouseMoveEvent( QGraphicsSceneMouseEvent* event )
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 
594 void QgsComposerMouseHandles::mouseReleaseEvent( QGraphicsSceneMouseEvent* event )
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, QLatin1String( "" ), 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, QLatin1String( "" ), 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 
717 void QgsComposerMouseHandles::mousePressEvent( QGraphicsSceneMouseEvent* event )
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 
751 void QgsComposerMouseHandles::mouseDoubleClickEvent( QGraphicsSceneMouseEvent *event )
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
1077  QTransform itemTransform;
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  QPen pen = QPen( QColor( Qt::red ) );
1171  pen.setWidthF( 0.0 );
1172  mHAlignSnapItem->setPen( pen );
1173  scene()->addItem( mHAlignSnapItem );
1174  mHAlignSnapItem->setZValue( 90 );
1175  }
1176  return mHAlignSnapItem;
1177 }
1178 
1179 QGraphicsLineItem* QgsComposerMouseHandles::vAlignSnapItem()
1180 {
1181  if ( !mVAlignSnapItem )
1182  {
1183  mVAlignSnapItem = new QGraphicsLineItem( nullptr );
1184  QPen pen = QPen( QColor( Qt::red ) );
1185  pen.setWidthF( 0.0 );
1186  mVAlignSnapItem->setPen( pen );
1187  scene()->addItem( mVAlignSnapItem );
1188  mVAlignSnapItem->setZValue( 90 );
1189  }
1190  return mVAlignSnapItem;
1191 }
1192 
1193 void QgsComposerMouseHandles::deleteHAlignSnapItem()
1194 {
1195  if ( mHAlignSnapItem )
1196  {
1197  scene()->removeItem( mHAlignSnapItem );
1198  delete mHAlignSnapItem;
1199  mHAlignSnapItem = nullptr;
1200  }
1201 }
1202 
1203 void QgsComposerMouseHandles::deleteVAlignSnapItem()
1204 {
1205  if ( mVAlignSnapItem )
1206  {
1207  scene()->removeItem( mVAlignSnapItem );
1208  delete mVAlignSnapItem;
1209  mVAlignSnapItem = nullptr;
1210  }
1211 }
1212 
1213 void QgsComposerMouseHandles::deleteAlignItems()
1214 {
1215  deleteHAlignSnapItem();
1216  deleteVAlignSnapItem();
1217 }
1218 
1219 QPointF QgsComposerMouseHandles::alignItem( double& alignX, double& alignY, double unalignedX, double unalignedY )
1220 {
1221  double left = unalignedX;
1222  double right = left + rect().width();
1223  double midH = ( left + right ) / 2.0;
1224  double top = unalignedY;
1225  double bottom = top + rect().height();
1226  double midV = ( top + bottom ) / 2.0;
1227 
1228  QMap<double, const QgsComposerItem* > xAlignCoordinates;
1229  QMap<double, const QgsComposerItem* > yAlignCoordinates;
1230  collectAlignCoordinates( xAlignCoordinates, yAlignCoordinates );
1231 
1232  //find nearest matches x
1233  double xItemLeft = left; //new left coordinate of the item
1234  double xAlignCoord = 0;
1235  double smallestDiffX = DBL_MAX;
1236 
1237  checkNearestItem( left, xAlignCoordinates, smallestDiffX, 0, xItemLeft, xAlignCoord );
1238  checkNearestItem( midH, xAlignCoordinates, smallestDiffX, ( left - right ) / 2.0, xItemLeft, xAlignCoord );
1239  checkNearestItem( right, xAlignCoordinates, smallestDiffX, left - right, xItemLeft, xAlignCoord );
1240 
1241  //find nearest matches y
1242  double yItemTop = top; //new top coordinate of the item
1243  double yAlignCoord = 0;
1244  double smallestDiffY = DBL_MAX;
1245 
1246  checkNearestItem( top, yAlignCoordinates, smallestDiffY, 0, yItemTop, yAlignCoord );
1247  checkNearestItem( midV, yAlignCoordinates, smallestDiffY, ( top - bottom ) / 2.0, yItemTop, yAlignCoord );
1248  checkNearestItem( bottom, yAlignCoordinates, smallestDiffY, top - bottom, yItemTop, yAlignCoord );
1249 
1250  double xCoord = ( smallestDiffX < 5 ) ? xItemLeft : unalignedX;
1251  alignX = ( smallestDiffX < 5 ) ? xAlignCoord : -1;
1252  double yCoord = ( smallestDiffY < 5 ) ? yItemTop : unalignedY;
1253  alignY = ( smallestDiffY < 5 ) ? yAlignCoord : -1;
1254  return QPointF( xCoord, yCoord );
1255 }
1256 
1257 QPointF QgsComposerMouseHandles::alignPos( QPointF pos, double& alignX, double& alignY )
1258 {
1259  QMap<double, const QgsComposerItem* > xAlignCoordinates;
1260  QMap<double, const QgsComposerItem* > yAlignCoordinates;
1261  collectAlignCoordinates( xAlignCoordinates, yAlignCoordinates );
1262 
1263  double nearestX = pos.x();
1264  double nearestY = pos.y();
1265  if ( !nearestItem( xAlignCoordinates, pos.x(), nearestX )
1266  || !nearestItem( yAlignCoordinates, pos.y(), nearestY ) )
1267  {
1268  alignX = -1;
1269  alignY = -1;
1270  return pos;
1271  }
1272 
1273  //convert snap tolerance from pixels to mm
1274  double viewScaleFactor = graphicsView()->transform().m11();
1275  double alignThreshold = mComposition->snapTolerance() / viewScaleFactor;
1276 
1277  QPointF result( pos.x(), pos.y() );
1278  if ( fabs( nearestX - pos.x() ) < alignThreshold )
1279  {
1280  result.setX( nearestX );
1281  alignX = nearestX;
1282  }
1283  else
1284  {
1285  alignX = -1;
1286  }
1287 
1288  if ( fabs( nearestY - pos.y() ) < alignThreshold )
1289  {
1290  result.setY( nearestY );
1291  alignY = nearestY;
1292  }
1293  else
1294  {
1295  alignY = -1;
1296  }
1297  return result;
1298 }
1299 
1300 void QgsComposerMouseHandles::collectAlignCoordinates( QMap< double, const QgsComposerItem* >& alignCoordsX, QMap< double, const QgsComposerItem* >& alignCoordsY )
1301 {
1302  alignCoordsX.clear();
1303  alignCoordsY.clear();
1304 
1305  if ( mComposition->smartGuidesEnabled() )
1306  {
1307  QList<QGraphicsItem *> itemList = mComposition->items();
1308  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
1309  for ( ; itemIt != itemList.end(); ++itemIt )
1310  {
1311  const QgsComposerItem* currentItem = dynamic_cast<const QgsComposerItem *>( *itemIt );
1312  //don't snap to selected items, since they're the ones that will be snapping to something else
1313  //also ignore group members - only snap to bounds of group itself
1314  //also ignore hidden items
1315  if ( !currentItem || currentItem->selected() || currentItem->isGroupMember() || !currentItem->isVisible() )
1316  {
1317  continue;
1318  }
1319  QRectF itemRect;
1320  if ( dynamic_cast<const QgsPaperItem *>( *itemIt ) )
1321  {
1322  //if snapping to paper use the paper item's rect rather then the bounding rect,
1323  //since we want to snap to the page edge and not any outlines drawn around the page
1324  itemRect = currentItem->mapRectToScene( currentItem->rect() );
1325  }
1326  else
1327  {
1328  itemRect = currentItem->mapRectToScene( currentItem->rectWithFrame() );
1329  }
1330  alignCoordsX.insert( itemRect.left(), currentItem );
1331  alignCoordsX.insert( itemRect.right(), currentItem );
1332  alignCoordsX.insert( itemRect.center().x(), currentItem );
1333  alignCoordsY.insert( itemRect.top(), currentItem );
1334  alignCoordsY.insert( itemRect.center().y(), currentItem );
1335  alignCoordsY.insert( itemRect.bottom(), currentItem );
1336  }
1337  }
1338 
1339  //arbitrary snap lines
1340  if ( mComposition->alignmentSnap() )
1341  {
1342  QList< QGraphicsLineItem* >::const_iterator sIt = mComposition->snapLines()->constBegin();
1343  for ( ; sIt != mComposition->snapLines()->constEnd(); ++sIt )
1344  {
1345  double x = ( *sIt )->line().x1();
1346  double y = ( *sIt )->line().y1();
1347  if ( qgsDoubleNear( y, 0.0 ) )
1348  {
1349  alignCoordsX.insert( x, nullptr );
1350  }
1351  else
1352  {
1353  alignCoordsY.insert( y, nullptr );
1354  }
1355  }
1356  }
1357 }
1358 
1359 void QgsComposerMouseHandles::checkNearestItem( double checkCoord, const QMap< double, const QgsComposerItem* >& alignCoords, double& smallestDiff, double itemCoordOffset, double& itemCoord, double& alignCoord )
1360 {
1361  double currentCoord = 0;
1362  if ( !nearestItem( alignCoords, checkCoord, currentCoord ) )
1363  {
1364  return;
1365  }
1366 
1367  double currentDiff = fabs( checkCoord - currentCoord );
1368  //convert snap tolerance from pixels to mm
1369  double viewScaleFactor = graphicsView()->transform().m11();
1370  double alignThreshold = mComposition->snapTolerance() / viewScaleFactor;
1371 
1372  if ( currentDiff < alignThreshold && currentDiff < smallestDiff )
1373  {
1374  itemCoord = currentCoord + itemCoordOffset;
1375  alignCoord = currentCoord;
1376  smallestDiff = currentDiff;
1377  }
1378 }
1379 
1380 bool QgsComposerMouseHandles::nearestItem( const QMap< double, const QgsComposerItem* >& coords, double value, double& nearestValue ) const
1381 {
1382  if ( coords.size() < 1 )
1383  {
1384  return false;
1385  }
1386 
1387  QMap< double, const QgsComposerItem* >::const_iterator it = coords.lowerBound( value );
1388  if ( it == coords.constBegin() ) //value smaller than first map value
1389  {
1390  nearestValue = it.key();
1391  return true;
1392  }
1393  else if ( it == coords.constEnd() ) //value larger than last map value
1394  {
1395  --it;
1396  nearestValue = it.key();
1397  return true;
1398  }
1399  else
1400  {
1401  //get smaller value and larger value and return the closer one
1402  double upperVal = it.key();
1403  --it;
1404  double lowerVal = it.key();
1405 
1406  double lowerDiff = value - lowerVal;
1407  double upperDiff = upperVal - value;
1408  if ( lowerDiff < upperDiff )
1409  {
1410  nearestValue = lowerVal;
1411  return true;
1412  }
1413  else
1414  {
1415  nearestValue = upperVal;
1416  return true;
1417  }
1418  }
1419 }
1420 
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:379
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 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:196
bool preventCursorChange() const
QgsComposition::PlotStyle plotStyle() const
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.
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...
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:348
double paperHeight() const
Height of paper item.
double paperWidth() const
Width of paper item.
QgsComposerMouseHandles(QgsComposition *composition)
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.
QList< QGraphicsLineItem *> * snapLines()
Returns pointer to snap lines collection.