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