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