QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
qgsgraphicsviewmousehandles.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsgraphicsviewmousehandles.cpp
3  ------------------------
4  begin : March 2020
5  copyright : (C) 2020 by Nyall Dawson
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 
19 #include "qgsrendercontext.h"
20 #include "qgis.h"
21 #include <QGraphicsView>
22 #include <QGraphicsSceneHoverEvent>
23 #include <QPainter>
24 #include <QWidget>
25 #include <limits>
26 
28 
29 QgsGraphicsViewMouseHandles::QgsGraphicsViewMouseHandles( QGraphicsView *view )
30  : QObject( nullptr )
31  , QGraphicsRectItem( nullptr )
32  , mView( view )
33 {
34  //accept hover events, required for changing cursor to resize cursors
35  setAcceptHoverEvents( true );
36 }
37 
38 void QgsGraphicsViewMouseHandles::paintInternal( QPainter *painter, bool showHandles, bool showStaticBoundingBoxes, bool showTemporaryBoundingBoxes, const QStyleOptionGraphicsItem *, QWidget * )
39 {
40  if ( !showHandles )
41  {
42  return;
43  }
44 
45  if ( showStaticBoundingBoxes )
46  {
47  //draw resize handles around bounds of entire selection
48  double rectHandlerSize = rectHandlerBorderTolerance();
49  drawHandles( painter, rectHandlerSize );
50  }
51 
52  if ( showTemporaryBoundingBoxes && ( mIsResizing || mIsDragging || showStaticBoundingBoxes ) )
53  {
54  //draw dotted boxes around selected items
55  drawSelectedItemBounds( painter );
56  }
57 }
58 
59 QRectF QgsGraphicsViewMouseHandles::storedItemRect( QGraphicsItem *item ) const
60 {
61  return itemRect( item );
62 }
63 
64 void QgsGraphicsViewMouseHandles::previewItemMove( QGraphicsItem *, double, double )
65 {
66 
67 }
68 
69 QRectF QgsGraphicsViewMouseHandles::previewSetItemRect( QGraphicsItem *, QRectF )
70 {
71  return QRectF();
72 }
73 
74 void QgsGraphicsViewMouseHandles::startMacroCommand( const QString & )
75 {
76 
77 }
78 
79 void QgsGraphicsViewMouseHandles::endMacroCommand()
80 {
81 
82 }
83 
84 void QgsGraphicsViewMouseHandles::endItemCommand( QGraphicsItem * )
85 {
86 
87 }
88 
89 void QgsGraphicsViewMouseHandles::createItemCommand( QGraphicsItem * )
90 {
91 
92 }
93 
94 QPointF QgsGraphicsViewMouseHandles::snapPoint( QPointF originalPoint, QgsGraphicsViewMouseHandles::SnapGuideMode, bool, bool )
95 {
96  return originalPoint;
97 }
98 
99 void QgsGraphicsViewMouseHandles::expandItemList( const QList<QGraphicsItem *> &items, QList<QGraphicsItem *> &collected ) const
100 {
101  collected = items;
102 }
103 
104 void QgsGraphicsViewMouseHandles::drawHandles( QPainter *painter, double rectHandlerSize )
105 {
106  //blue, zero width cosmetic pen for outline
107  QPen handlePen = QPen( QColor( 55, 140, 195, 255 ) );
108  handlePen.setWidth( 0 );
109  painter->setPen( handlePen );
110 
111  //draw box around entire selection bounds
112  painter->setBrush( Qt::NoBrush );
113  painter->drawRect( QRectF( 0, 0, rect().width(), rect().height() ) );
114 
115  //draw resize handles, using a filled white box
116  painter->setBrush( QColor( 255, 255, 255, 255 ) );
117  //top left
118  painter->drawRect( QRectF( 0, 0, rectHandlerSize, rectHandlerSize ) );
119  //mid top
120  painter->drawRect( QRectF( ( rect().width() - rectHandlerSize ) / 2, 0, rectHandlerSize, rectHandlerSize ) );
121  //top right
122  painter->drawRect( QRectF( rect().width() - rectHandlerSize, 0, rectHandlerSize, rectHandlerSize ) );
123  //mid left
124  painter->drawRect( QRectF( 0, ( rect().height() - rectHandlerSize ) / 2, rectHandlerSize, rectHandlerSize ) );
125  //mid right
126  painter->drawRect( QRectF( rect().width() - rectHandlerSize, ( rect().height() - rectHandlerSize ) / 2, rectHandlerSize, rectHandlerSize ) );
127  //bottom left
128  painter->drawRect( QRectF( 0, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) );
129  //mid bottom
130  painter->drawRect( QRectF( ( rect().width() - rectHandlerSize ) / 2, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) );
131  //bottom right
132  painter->drawRect( QRectF( rect().width() - rectHandlerSize, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) );
133 }
134 
135 void QgsGraphicsViewMouseHandles::drawSelectedItemBounds( QPainter *painter )
136 {
137  //draw dotted border around selected items to give visual feedback which items are selected
138  const QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
139  if ( selectedItems.isEmpty() )
140  {
141  return;
142  }
143 
144  //use difference mode so that they are visible regardless of item colors
145  QgsScopedQPainterState painterState( painter );
146  painter->setCompositionMode( QPainter::CompositionMode_Difference );
147 
148  // use a grey dashed pen - in difference mode this should always be visible
149  QPen selectedItemPen = QPen( QColor( 144, 144, 144, 255 ) );
150  selectedItemPen.setStyle( Qt::DashLine );
151  selectedItemPen.setWidth( 0 );
152  painter->setPen( selectedItemPen );
153  painter->setBrush( Qt::NoBrush );
154 
155  QList< QGraphicsItem * > itemsToDraw;
156  expandItemList( selectedItems, itemsToDraw );
157 
158  for ( QGraphicsItem *item : std::as_const( itemsToDraw ) )
159  {
160  //get bounds of selected item
161  QPolygonF itemBounds;
162  if ( isDragging() && !itemIsLocked( item ) )
163  {
164  //if currently dragging, draw selected item bounds relative to current mouse position
165  //first, get bounds of current item in scene coordinates
166  QPolygonF itemSceneBounds = item->mapToScene( itemRect( item ) );
167  //now, translate it by the current movement amount
168  //IMPORTANT - this is done in scene coordinates, since we don't want any rotation/non-translation transforms to affect the movement
169  itemSceneBounds.translate( transform().dx(), transform().dy() );
170  //finally, remap it to the mouse handle item's coordinate system so it's ready for drawing
171  itemBounds = mapFromScene( itemSceneBounds );
172  }
173  else if ( isResizing() && !itemIsLocked( item ) )
174  {
175  //if currently resizing, calculate relative resize of this item
176  if ( selectedItems.size() > 1 )
177  {
178  //get item bounds in mouse handle item's coordinate system
179  QRectF thisItemRect = mapRectFromItem( item, itemRect( item ) );
180  //now, resize it relative to the current resized dimensions of the mouse handles
181  relativeResizeRect( thisItemRect, QRectF( -mResizeMoveX, -mResizeMoveY, mBeginHandleWidth, mBeginHandleHeight ), mResizeRect );
182  itemBounds = QPolygonF( thisItemRect );
183  }
184  else
185  {
186  //single item selected
187  itemBounds = rect();
188  }
189  }
190  else
191  {
192  //not resizing or moving, so just map from scene bounds
193  itemBounds = mapRectFromItem( item, itemRect( item ) );
194  }
195 
196  // drawPolygon causes issues on windows - corners of path may be missing resulting in triangles being drawn
197  // instead of rectangles! (Same cause as #13343)
198  QPainterPath path;
199  path.addPolygon( itemBounds );
200  painter->drawPath( path );
201  }
202 }
203 
204 double QgsGraphicsViewMouseHandles::rectHandlerBorderTolerance()
205 {
206  if ( !mView )
207  return 0;
208 
209  //calculate size for resize handles
210  //get view scale factor
211  double viewScaleFactor = mView->transform().m11();
212 
213  //size of handle boxes depends on zoom level in layout view
214  double rectHandlerSize = mHandleSize / viewScaleFactor;
215 
216  //make sure the boxes don't get too large
217  if ( rectHandlerSize > ( rect().width() / 3 ) )
218  {
219  rectHandlerSize = rect().width() / 3;
220  }
221  if ( rectHandlerSize > ( rect().height() / 3 ) )
222  {
223  rectHandlerSize = rect().height() / 3;
224  }
225  return rectHandlerSize;
226 }
227 
228 Qt::CursorShape QgsGraphicsViewMouseHandles::cursorForPosition( QPointF itemCoordPos )
229 {
230  QgsGraphicsViewMouseHandles::MouseAction mouseAction = mouseActionForPosition( itemCoordPos );
231  switch ( mouseAction )
232  {
233  case NoAction:
234  return Qt::ForbiddenCursor;
235  case MoveItem:
236  return Qt::SizeAllCursor;
237  case ResizeUp:
238  case ResizeDown:
239  //account for rotation
240  if ( ( rotation() <= 22.5 || rotation() >= 337.5 ) || ( rotation() >= 157.5 && rotation() <= 202.5 ) )
241  {
242  return Qt::SizeVerCursor;
243  }
244  else if ( ( rotation() >= 22.5 && rotation() <= 67.5 ) || ( rotation() >= 202.5 && rotation() <= 247.5 ) )
245  {
246  return Qt::SizeBDiagCursor;
247  }
248  else if ( ( rotation() >= 67.5 && rotation() <= 112.5 ) || ( rotation() >= 247.5 && rotation() <= 292.5 ) )
249  {
250  return Qt::SizeHorCursor;
251  }
252  else
253  {
254  return Qt::SizeFDiagCursor;
255  }
256  case ResizeLeft:
257  case ResizeRight:
258  //account for rotation
259  if ( ( rotation() <= 22.5 || rotation() >= 337.5 ) || ( rotation() >= 157.5 && rotation() <= 202.5 ) )
260  {
261  return Qt::SizeHorCursor;
262  }
263  else if ( ( rotation() >= 22.5 && rotation() <= 67.5 ) || ( rotation() >= 202.5 && rotation() <= 247.5 ) )
264  {
265  return Qt::SizeFDiagCursor;
266  }
267  else if ( ( rotation() >= 67.5 && rotation() <= 112.5 ) || ( rotation() >= 247.5 && rotation() <= 292.5 ) )
268  {
269  return Qt::SizeVerCursor;
270  }
271  else
272  {
273  return Qt::SizeBDiagCursor;
274  }
275 
276  case ResizeLeftUp:
277  case ResizeRightDown:
278  //account for rotation
279  if ( ( rotation() <= 22.5 || rotation() >= 337.5 ) || ( rotation() >= 157.5 && rotation() <= 202.5 ) )
280  {
281  return Qt::SizeFDiagCursor;
282  }
283  else if ( ( rotation() >= 22.5 && rotation() <= 67.5 ) || ( rotation() >= 202.5 && rotation() <= 247.5 ) )
284  {
285  return Qt::SizeVerCursor;
286  }
287  else if ( ( rotation() >= 67.5 && rotation() <= 112.5 ) || ( rotation() >= 247.5 && rotation() <= 292.5 ) )
288  {
289  return Qt::SizeBDiagCursor;
290  }
291  else
292  {
293  return Qt::SizeHorCursor;
294  }
295  case ResizeRightUp:
296  case ResizeLeftDown:
297  //account for rotation
298  if ( ( rotation() <= 22.5 || rotation() >= 337.5 ) || ( rotation() >= 157.5 && rotation() <= 202.5 ) )
299  {
300  return Qt::SizeBDiagCursor;
301  }
302  else if ( ( rotation() >= 22.5 && rotation() <= 67.5 ) || ( rotation() >= 202.5 && rotation() <= 247.5 ) )
303  {
304  return Qt::SizeHorCursor;
305  }
306  else if ( ( rotation() >= 67.5 && rotation() <= 112.5 ) || ( rotation() >= 247.5 && rotation() <= 292.5 ) )
307  {
308  return Qt::SizeFDiagCursor;
309  }
310  else
311  {
312  return Qt::SizeVerCursor;
313  }
314  case SelectItem:
315  return Qt::ArrowCursor;
316  }
317 
318  return Qt::ArrowCursor;
319 }
320 
321 QgsGraphicsViewMouseHandles::MouseAction QgsGraphicsViewMouseHandles::mouseActionForPosition( QPointF itemCoordPos )
322 {
323  bool nearLeftBorder = false;
324  bool nearRightBorder = false;
325  bool nearLowerBorder = false;
326  bool nearUpperBorder = false;
327 
328  bool withinWidth = false;
329  bool withinHeight = false;
330  if ( itemCoordPos.x() >= 0 && itemCoordPos.x() <= rect().width() )
331  {
332  withinWidth = true;
333  }
334  if ( itemCoordPos.y() >= 0 && itemCoordPos.y() <= rect().height() )
335  {
336  withinHeight = true;
337  }
338 
339  double borderTolerance = rectHandlerBorderTolerance();
340 
341  if ( itemCoordPos.x() >= 0 && itemCoordPos.x() < borderTolerance )
342  {
343  nearLeftBorder = true;
344  }
345  if ( itemCoordPos.y() >= 0 && itemCoordPos.y() < borderTolerance )
346  {
347  nearUpperBorder = true;
348  }
349  if ( itemCoordPos.x() <= rect().width() && itemCoordPos.x() > ( rect().width() - borderTolerance ) )
350  {
351  nearRightBorder = true;
352  }
353  if ( itemCoordPos.y() <= rect().height() && itemCoordPos.y() > ( rect().height() - borderTolerance ) )
354  {
355  nearLowerBorder = true;
356  }
357 
358  if ( nearLeftBorder && nearUpperBorder )
359  {
360  return QgsGraphicsViewMouseHandles::ResizeLeftUp;
361  }
362  else if ( nearLeftBorder && nearLowerBorder )
363  {
364  return QgsGraphicsViewMouseHandles::ResizeLeftDown;
365  }
366  else if ( nearRightBorder && nearUpperBorder )
367  {
368  return QgsGraphicsViewMouseHandles::ResizeRightUp;
369  }
370  else if ( nearRightBorder && nearLowerBorder )
371  {
372  return QgsGraphicsViewMouseHandles::ResizeRightDown;
373  }
374  else if ( nearLeftBorder && withinHeight )
375  {
376  return QgsGraphicsViewMouseHandles::ResizeLeft;
377  }
378  else if ( nearRightBorder && withinHeight )
379  {
380  return QgsGraphicsViewMouseHandles::ResizeRight;
381  }
382  else if ( nearUpperBorder && withinWidth )
383  {
384  return QgsGraphicsViewMouseHandles::ResizeUp;
385  }
386  else if ( nearLowerBorder && withinWidth )
387  {
388  return QgsGraphicsViewMouseHandles::ResizeDown;
389  }
390 
391  //find out if cursor position is over a selected item
392  QPointF scenePoint = mapToScene( itemCoordPos );
393  const QList<QGraphicsItem *> itemsAtCursorPos = sceneItemsAtPoint( scenePoint );
394  if ( itemsAtCursorPos.isEmpty() )
395  {
396  //no items at cursor position
397  return QgsGraphicsViewMouseHandles::SelectItem;
398  }
399  for ( QGraphicsItem *graphicsItem : itemsAtCursorPos )
400  {
401  if ( graphicsItem && graphicsItem->isSelected() )
402  {
403  //cursor is over a selected layout item
404  return QgsGraphicsViewMouseHandles::MoveItem;
405  }
406  }
407 
408  //default
409  return QgsGraphicsViewMouseHandles::SelectItem;
410 }
411 
412 QgsGraphicsViewMouseHandles::MouseAction QgsGraphicsViewMouseHandles::mouseActionForScenePos( QPointF sceneCoordPos )
413 {
414  // convert sceneCoordPos to item coordinates
415  QPointF itemPos = mapFromScene( sceneCoordPos );
416  return mouseActionForPosition( itemPos );
417 }
418 
419 bool QgsGraphicsViewMouseHandles::shouldBlockEvent( QInputEvent * ) const
420 {
421  return mIsDragging || mIsResizing;
422 }
423 
424 void QgsGraphicsViewMouseHandles::selectedItemSizeChanged()
425 {
426  if ( !isDragging() && !isResizing() )
427  {
428  //only required for non-mouse initiated size changes
429  updateHandles();
430  }
431 }
432 
433 void QgsGraphicsViewMouseHandles::selectedItemRotationChanged()
434 {
435  if ( !isDragging() && !isResizing() )
436  {
437  //only required for non-mouse initiated rotation changes
438  updateHandles();
439  }
440 }
441 
442 void QgsGraphicsViewMouseHandles::hoverMoveEvent( QGraphicsSceneHoverEvent *event )
443 {
444  setViewportCursor( cursorForPosition( event->pos() ) );
445 }
446 
447 void QgsGraphicsViewMouseHandles::hoverLeaveEvent( QGraphicsSceneHoverEvent *event )
448 {
449  Q_UNUSED( event )
450  setViewportCursor( Qt::ArrowCursor );
451 }
452 
453 void QgsGraphicsViewMouseHandles::mousePressEvent( QGraphicsSceneMouseEvent *event )
454 {
455  if ( event->button() != Qt::LeftButton )
456  {
457  event->ignore();
458  return;
459  }
460 
461  //save current cursor position
462  mMouseMoveStartPos = event->lastScenePos();
463  mLastMouseEventPos = event->lastScenePos();
464  //save current item geometry
465  mBeginMouseEventPos = event->lastScenePos();
466  mBeginHandlePos = scenePos();
467  mBeginHandleWidth = rect().width();
468  mBeginHandleHeight = rect().height();
469  //type of mouse move action
470  mCurrentMouseMoveAction = mouseActionForPosition( event->pos() );
471 
472  hideAlignItems();
473 
474  if ( mCurrentMouseMoveAction == MoveItem )
475  {
476  //moving items
477  mIsDragging = true;
478  }
479  else if ( mCurrentMouseMoveAction != SelectItem &&
480  mCurrentMouseMoveAction != NoAction )
481  {
482  //resizing items
483  mIsResizing = true;
484  mResizeRect = QRectF( 0, 0, mBeginHandleWidth, mBeginHandleHeight );
485  mResizeMoveX = 0;
486  mResizeMoveY = 0;
487  mCursorOffset = calcCursorEdgeOffset( mMouseMoveStartPos );
488 
489  }
490 }
491 
492 void QgsGraphicsViewMouseHandles::resetStatusBar()
493 {
494  const QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
495  int selectedCount = selectedItems.size();
496  if ( selectedCount > 1 )
497  {
498  //set status bar message to count of selected items
499  showStatusMessage( tr( "%1 items selected" ).arg( selectedCount ) );
500  }
501  else if ( selectedCount == 1 )
502  {
503  //set status bar message to count of selected items
504  showStatusMessage( tr( "1 item selected" ) );
505  }
506  else
507  {
508  //clear status bar message
509  showStatusMessage( QString() );
510  }
511 }
512 
513 void QgsGraphicsViewMouseHandles::mouseMoveEvent( QGraphicsSceneMouseEvent *event )
514 {
515  if ( isDragging() )
516  {
517  //currently dragging a selection
518  //if shift depressed, constrain movement to horizontal/vertical
519  //if control depressed, ignore snapping
520  dragMouseMove( event->lastScenePos(), event->modifiers() & Qt::ShiftModifier, event->modifiers() & Qt::ControlModifier );
521  }
522  else if ( isResizing() )
523  {
524  //currently resizing a selection
525  //lock aspect ratio if shift depressed
526  //resize from center if alt depressed
527  resizeMouseMove( event->lastScenePos(), event->modifiers() & Qt::ShiftModifier, event->modifiers() & Qt::AltModifier );
528  }
529 
530  mLastMouseEventPos = event->lastScenePos();
531 }
532 
533 void QgsGraphicsViewMouseHandles::mouseReleaseEvent( QGraphicsSceneMouseEvent *event )
534 {
535  if ( event->button() != Qt::LeftButton )
536  {
537  event->ignore();
538  return;
539  }
540 
541  QPointF mouseMoveStopPoint = event->lastScenePos();
542  double diffX = mouseMoveStopPoint.x() - mMouseMoveStartPos.x();
543  double diffY = mouseMoveStopPoint.y() - mMouseMoveStartPos.y();
544 
545  //it was only a click
546  if ( std::fabs( diffX ) < std::numeric_limits<double>::min() && std::fabs( diffY ) < std::numeric_limits<double>::min() )
547  {
548  mIsDragging = false;
549  mIsResizing = false;
550  update();
551  hideAlignItems();
552  return;
553  }
554 
555  if ( mCurrentMouseMoveAction == MoveItem )
556  {
557  //move selected items
558  startMacroCommand( tr( "Move Items" ) );
559 
560  QPointF mEndHandleMovePos = scenePos();
561 
562  double deltaX = mEndHandleMovePos.x() - mBeginHandlePos.x();
563  double deltaY = mEndHandleMovePos.y() - mBeginHandlePos.y();
564 
565  //move all selected items
566  const QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
567  for ( QGraphicsItem *item : selectedItems )
568  {
569  if ( itemIsLocked( item ) || ( item->flags() & QGraphicsItem::ItemIsSelectable ) == 0 || itemIsGroupMember( item ) )
570  {
571  //don't move locked items, or grouped items (group takes care of that)
572  continue;
573  }
574 
575  createItemCommand( item );
576  moveItem( item, deltaX, deltaY );
577  endItemCommand( item );
578  }
579  endMacroCommand();
580  }
581  else if ( mCurrentMouseMoveAction != NoAction )
582  {
583  //resize selected items
584  startMacroCommand( tr( "Resize Items" ) );
585 
586  //resize all selected items
587  const QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
588  for ( QGraphicsItem *item : selectedItems )
589  {
590  if ( itemIsLocked( item ) || ( item->flags() & QGraphicsItem::ItemIsSelectable ) == 0 )
591  {
592  //don't resize locked items or deselectable items (e.g., items which make up an item group)
593  continue;
594  }
595  createItemCommand( item );
596 
597  QRectF thisItemRect;
598  if ( selectedItems.size() == 1 )
599  {
600  //only a single item is selected, so set its size to the final resized mouse handle size
601  thisItemRect = mResizeRect;
602  }
603  else
604  {
605  //multiple items selected, so each needs to be scaled relatively to the final size of the mouse handles
606  thisItemRect = mapRectFromItem( item, itemRect( item ) );
607  relativeResizeRect( thisItemRect, QRectF( -mResizeMoveX, -mResizeMoveY, mBeginHandleWidth, mBeginHandleHeight ), mResizeRect );
608  }
609 
610  thisItemRect = thisItemRect.normalized();
611  QPointF newPos = mapToScene( thisItemRect.topLeft() );
612  thisItemRect.moveTopLeft( newPos );
613  setItemRect( item, thisItemRect );
614 
615  endItemCommand( item );
616  }
617  endMacroCommand();
618  }
619 
620  hideAlignItems();
621  if ( mIsDragging )
622  {
623  mIsDragging = false;
624  }
625  if ( mIsResizing )
626  {
627  mIsResizing = false;
628  }
629 
630  //reset default action
631  mCurrentMouseMoveAction = MoveItem;
632  setViewportCursor( Qt::ArrowCursor );
633  //redraw handles
634  resetTransform();
635  updateHandles();
636  //reset status bar message
637  resetStatusBar();
638 }
639 
640 bool QgsGraphicsViewMouseHandles::selectionRotation( double &rotation ) const
641 {
642  //check if all selected items have same rotation
643  QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
644  auto itemIter = selectedItems.constBegin();
645 
646  //start with rotation of first selected item
647  double firstItemRotation = ( *itemIter )->rotation();
648 
649  //iterate through remaining items, checking if they have same rotation
650  for ( ++itemIter; itemIter != selectedItems.constEnd(); ++itemIter )
651  {
652  if ( !qgsDoubleNear( ( *itemIter )->rotation(), firstItemRotation ) )
653  {
654  //item has a different rotation, so return false
655  return false;
656  }
657  }
658 
659  //all items have the same rotation, so set the rotation variable and return true
660  rotation = firstItemRotation;
661  return true;
662 }
663 
664 void QgsGraphicsViewMouseHandles::updateHandles()
665 {
666  //recalculate size and position of handle item
667 
668  //first check to see if any items are selected
669  QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
670  if ( !selectedItems.isEmpty() )
671  {
672  //one or more items are selected, get bounds of all selected items
673 
674  //update rotation of handle object
675  double rotation;
676  if ( selectionRotation( rotation ) )
677  {
678  //all items share a common rotation value, so we rotate the mouse handles to match
679  setRotation( rotation );
680  }
681  else
682  {
683  //items have varying rotation values - we can't rotate the mouse handles to match
684  setRotation( 0 );
685  }
686 
687  //get bounds of all selected items
688  QRectF newHandleBounds = selectionBounds();
689 
690  //update size and position of handle object
691  setRect( 0, 0, newHandleBounds.width(), newHandleBounds.height() );
692  setPos( mapToScene( newHandleBounds.topLeft() ) );
693 
694  show();
695  }
696  else
697  {
698  //no items selected, hide handles
699  hide();
700  }
701  //force redraw
702  update();
703 }
704 
705 void QgsGraphicsViewMouseHandles::dragMouseMove( QPointF currentPosition, bool lockMovement, bool preventSnap )
706 {
707  if ( !scene() )
708  {
709  return;
710  }
711 
712  //calculate total amount of mouse movement since drag began
713  double moveX = currentPosition.x() - mBeginMouseEventPos.x();
714  double moveY = currentPosition.y() - mBeginMouseEventPos.y();
715 
716  //find target position before snapping (in scene coordinates)
717  QPointF upperLeftPoint( mBeginHandlePos.x() + moveX, mBeginHandlePos.y() + moveY );
718 
719  QPointF snappedLeftPoint;
720 
721  //no snapping for rotated items for now
722  if ( !preventSnap && qgsDoubleNear( rotation(), 0.0 ) )
723  {
724  //snap to grid and guides
725  snappedLeftPoint = snapPoint( upperLeftPoint, Item );
726  }
727  else
728  {
729  //no snapping
730  snappedLeftPoint = upperLeftPoint;
731  hideAlignItems();
732  }
733 
734  //calculate total shift for item from beginning of drag operation to current position
735  double moveRectX = snappedLeftPoint.x() - mBeginHandlePos.x();
736  double moveRectY = snappedLeftPoint.y() - mBeginHandlePos.y();
737 
738  if ( lockMovement )
739  {
740  //constrained (shift) moving should lock to horizontal/vertical movement
741  //reset the smaller of the x/y movements
742  if ( std::fabs( moveRectX ) <= std::fabs( moveRectY ) )
743  {
744  moveRectX = 0;
745  }
746  else
747  {
748  moveRectY = 0;
749  }
750  }
751 
752  //shift handle item to new position
753  QTransform moveTransform;
754  moveTransform.translate( moveRectX, moveRectY );
755  setTransform( moveTransform );
756 
757  const QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
758  for ( QGraphicsItem *item : selectedItems )
759  {
760  previewItemMove( item, moveRectX, moveRectY );
761  }
762  //show current displacement of selection in status bar
763  showStatusMessage( tr( "dx: %1 mm dy: %2 mm" ).arg( moveRectX ).arg( moveRectY ) );
764 }
765 
766 void QgsGraphicsViewMouseHandles::resizeMouseMove( QPointF currentPosition, bool lockRatio, bool fromCenter )
767 {
768  if ( !scene() )
769  {
770  return;
771  }
772 
773  double mx = 0.0, my = 0.0, rx = 0.0, ry = 0.0;
774 
775  QPointF beginMousePos;
776  QPointF finalPosition;
777  if ( qgsDoubleNear( rotation(), 0.0 ) )
778  {
779  //snapping only occurs if handles are not rotated for now
780 
781  bool snapVertical = mCurrentMouseMoveAction == ResizeLeft ||
782  mCurrentMouseMoveAction == ResizeRight ||
783  mCurrentMouseMoveAction == ResizeLeftUp ||
784  mCurrentMouseMoveAction == ResizeRightUp ||
785  mCurrentMouseMoveAction == ResizeLeftDown ||
786  mCurrentMouseMoveAction == ResizeRightDown;
787 
788  bool snapHorizontal = mCurrentMouseMoveAction == ResizeUp ||
789  mCurrentMouseMoveAction == ResizeDown ||
790  mCurrentMouseMoveAction == ResizeLeftUp ||
791  mCurrentMouseMoveAction == ResizeRightUp ||
792  mCurrentMouseMoveAction == ResizeLeftDown ||
793  mCurrentMouseMoveAction == ResizeRightDown;
794 
795  //subtract cursor edge offset from begin mouse event and current cursor position, so that snapping occurs to edge of mouse handles
796  //rather then cursor position
797  beginMousePos = mapFromScene( QPointF( mBeginMouseEventPos.x() - mCursorOffset.width(), mBeginMouseEventPos.y() - mCursorOffset.height() ) );
798  QPointF snappedPosition = snapPoint( QPointF( currentPosition.x() - mCursorOffset.width(), currentPosition.y() - mCursorOffset.height() ), Point, snapHorizontal, snapVertical );
799  finalPosition = mapFromScene( snappedPosition );
800  }
801  else
802  {
803  //no snapping for rotated items for now
804  beginMousePos = mapFromScene( mBeginMouseEventPos );
805  finalPosition = mapFromScene( currentPosition );
806  }
807 
808  double diffX = finalPosition.x() - beginMousePos.x();
809  double diffY = finalPosition.y() - beginMousePos.y();
810 
811  double ratio = 0;
812  if ( lockRatio && !qgsDoubleNear( mBeginHandleHeight, 0.0 ) )
813  {
814  ratio = mBeginHandleWidth / mBeginHandleHeight;
815  }
816 
817  switch ( mCurrentMouseMoveAction )
818  {
819  //vertical resize
820  case ResizeUp:
821  {
822  if ( ratio )
823  {
824  diffX = ( ( mBeginHandleHeight - diffY ) * ratio ) - mBeginHandleWidth;
825  mx = -diffX / 2;
826  my = diffY;
827  rx = diffX;
828  ry = -diffY;
829  }
830  else
831  {
832  mx = 0;
833  my = diffY;
834  rx = 0;
835  ry = -diffY;
836  }
837  break;
838  }
839 
840  case ResizeDown:
841  {
842  if ( ratio )
843  {
844  diffX = ( ( mBeginHandleHeight + diffY ) * ratio ) - mBeginHandleWidth;
845  mx = -diffX / 2;
846  my = 0;
847  rx = diffX;
848  ry = diffY;
849  }
850  else
851  {
852  mx = 0;
853  my = 0;
854  rx = 0;
855  ry = diffY;
856  }
857  break;
858  }
859 
860  //horizontal resize
861  case ResizeLeft:
862  {
863  if ( ratio )
864  {
865  diffY = ( ( mBeginHandleWidth - diffX ) / ratio ) - mBeginHandleHeight;
866  mx = diffX;
867  my = -diffY / 2;
868  rx = -diffX;
869  ry = diffY;
870  }
871  else
872  {
873  mx = diffX, my = 0;
874  rx = -diffX;
875  ry = 0;
876  }
877  break;
878  }
879 
880  case ResizeRight:
881  {
882  if ( ratio )
883  {
884  diffY = ( ( mBeginHandleWidth + diffX ) / ratio ) - mBeginHandleHeight;
885  mx = 0;
886  my = -diffY / 2;
887  rx = diffX;
888  ry = diffY;
889  }
890  else
891  {
892  mx = 0;
893  my = 0;
894  rx = diffX, ry = 0;
895  }
896  break;
897  }
898 
899  //diagonal resize
900  case ResizeLeftUp:
901  {
902  if ( ratio )
903  {
904  //ratio locked resize
905  if ( ( mBeginHandleWidth - diffX ) / ( mBeginHandleHeight - diffY ) > ratio )
906  {
907  diffX = mBeginHandleWidth - ( ( mBeginHandleHeight - diffY ) * ratio );
908  }
909  else
910  {
911  diffY = mBeginHandleHeight - ( ( mBeginHandleWidth - diffX ) / ratio );
912  }
913  }
914  mx = diffX, my = diffY;
915  rx = -diffX;
916  ry = -diffY;
917  break;
918  }
919 
920  case ResizeRightDown:
921  {
922  if ( ratio )
923  {
924  //ratio locked resize
925  if ( ( mBeginHandleWidth + diffX ) / ( mBeginHandleHeight + diffY ) > ratio )
926  {
927  diffX = ( ( mBeginHandleHeight + diffY ) * ratio ) - mBeginHandleWidth;
928  }
929  else
930  {
931  diffY = ( ( mBeginHandleWidth + diffX ) / ratio ) - mBeginHandleHeight;
932  }
933  }
934  mx = 0;
935  my = 0;
936  rx = diffX, ry = diffY;
937  break;
938  }
939 
940  case ResizeRightUp:
941  {
942  if ( ratio )
943  {
944  //ratio locked resize
945  if ( ( mBeginHandleWidth + diffX ) / ( mBeginHandleHeight - diffY ) > ratio )
946  {
947  diffX = ( ( mBeginHandleHeight - diffY ) * ratio ) - mBeginHandleWidth;
948  }
949  else
950  {
951  diffY = mBeginHandleHeight - ( ( mBeginHandleWidth + diffX ) / ratio );
952  }
953  }
954  mx = 0;
955  my = diffY, rx = diffX, ry = -diffY;
956  break;
957  }
958 
959  case ResizeLeftDown:
960  {
961  if ( ratio )
962  {
963  //ratio locked resize
964  if ( ( mBeginHandleWidth - diffX ) / ( mBeginHandleHeight + diffY ) > ratio )
965  {
966  diffX = mBeginHandleWidth - ( ( mBeginHandleHeight + diffY ) * ratio );
967  }
968  else
969  {
970  diffY = ( ( mBeginHandleWidth - diffX ) / ratio ) - mBeginHandleHeight;
971  }
972  }
973  mx = diffX, my = 0;
974  rx = -diffX;
975  ry = diffY;
976  break;
977  }
978 
979  case MoveItem:
980  case SelectItem:
981  case NoAction:
982  break;
983  }
984 
985  //resizing from center of objects?
986  if ( fromCenter )
987  {
988  my = -ry;
989  mx = -rx;
990  ry = 2 * ry;
991  rx = 2 * rx;
992  }
993 
994  //update selection handle rectangle
995 
996  //make sure selection handle size rectangle is normalized (ie, left coord < right coord)
997  mResizeMoveX = mBeginHandleWidth + rx > 0 ? mx : mx + mBeginHandleWidth + rx;
998  mResizeMoveY = mBeginHandleHeight + ry > 0 ? my : my + mBeginHandleHeight + ry;
999 
1000  //calculate movement in scene coordinates
1001  QLineF translateLine = QLineF( 0, 0, mResizeMoveX, mResizeMoveY );
1002  translateLine.setAngle( translateLine.angle() - rotation() );
1003  QPointF sceneTranslate = translateLine.p2();
1004 
1005  //move selection handles
1006  QTransform itemTransform;
1007  itemTransform.translate( sceneTranslate.x(), sceneTranslate.y() );
1008  setTransform( itemTransform );
1009 
1010  //handle non-normalised resizes - e.g., dragging the left handle so far to the right that it's past the right handle
1011  if ( mBeginHandleWidth + rx >= 0 && mBeginHandleHeight + ry >= 0 )
1012  {
1013  mResizeRect = QRectF( 0, 0, mBeginHandleWidth + rx, mBeginHandleHeight + ry );
1014  }
1015  else if ( mBeginHandleHeight + ry >= 0 )
1016  {
1017  mResizeRect = QRectF( QPointF( -( mBeginHandleWidth + rx ), 0 ), QPointF( 0, mBeginHandleHeight + ry ) );
1018  }
1019  else if ( mBeginHandleWidth + rx >= 0 )
1020  {
1021  mResizeRect = QRectF( QPointF( 0, -( mBeginHandleHeight + ry ) ), QPointF( mBeginHandleWidth + rx, 0 ) );
1022  }
1023  else
1024  {
1025  mResizeRect = QRectF( QPointF( -( mBeginHandleWidth + rx ), -( mBeginHandleHeight + ry ) ), QPointF( 0, 0 ) );
1026  }
1027 
1028  const QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
1029  QRectF newHandleBounds;
1030  for ( QGraphicsItem *item : selectedItems )
1031  {
1032  //get stored item bounds in mouse handle item's coordinate system
1033  QRectF thisItemRect = mapRectFromScene( storedItemRect( item ) );
1034  //now, resize it relative to the current resized dimensions of the mouse handles
1035  relativeResizeRect( thisItemRect, QRectF( -mResizeMoveX, -mResizeMoveY, mBeginHandleWidth, mBeginHandleHeight ), mResizeRect );
1036 
1037  thisItemRect = mapRectFromScene( previewSetItemRect( item, mapRectToScene( thisItemRect ) ) );
1038  newHandleBounds = newHandleBounds.isValid() ? newHandleBounds.united( thisItemRect ) : thisItemRect;
1039  }
1040 
1041  setRect( newHandleBounds.isValid() ? newHandleBounds : QRectF( 0, 0, std::fabs( mBeginHandleWidth + rx ), std::fabs( mBeginHandleHeight + ry ) ) );
1042 
1043  //show current size of selection in status bar
1044  showStatusMessage( tr( "width: %1 mm height: %2 mm" ).arg( rect().width() ).arg( rect().height() ) );
1045 
1046 }
1047 
1048 void QgsGraphicsViewMouseHandles::setHandleSize( double size )
1049 {
1050  mHandleSize = size;
1051 }
1052 
1053 void QgsGraphicsViewMouseHandles::mouseDoubleClickEvent( QGraphicsSceneMouseEvent *event )
1054 {
1055  Q_UNUSED( event )
1056 }
1057 
1058 QSizeF QgsGraphicsViewMouseHandles::calcCursorEdgeOffset( QPointF cursorPos )
1059 {
1060  //find offset between cursor position and actual edge of item
1061  QPointF sceneMousePos = mapFromScene( cursorPos );
1062 
1063  switch ( mCurrentMouseMoveAction )
1064  {
1065  //vertical resize
1066  case QgsGraphicsViewMouseHandles::ResizeUp:
1067  return QSizeF( 0, sceneMousePos.y() );
1068 
1069  case QgsGraphicsViewMouseHandles::ResizeDown:
1070  return QSizeF( 0, sceneMousePos.y() - rect().height() );
1071 
1072  //horizontal resize
1073  case QgsGraphicsViewMouseHandles::ResizeLeft:
1074  return QSizeF( sceneMousePos.x(), 0 );
1075 
1076  case QgsGraphicsViewMouseHandles::ResizeRight:
1077  return QSizeF( sceneMousePos.x() - rect().width(), 0 );
1078 
1079  //diagonal resize
1080  case QgsGraphicsViewMouseHandles::ResizeLeftUp:
1081  return QSizeF( sceneMousePos.x(), sceneMousePos.y() );
1082 
1083  case QgsGraphicsViewMouseHandles::ResizeRightDown:
1084  return QSizeF( sceneMousePos.x() - rect().width(), sceneMousePos.y() - rect().height() );
1085 
1086  case QgsGraphicsViewMouseHandles::ResizeRightUp:
1087  return QSizeF( sceneMousePos.x() - rect().width(), sceneMousePos.y() );
1088 
1089  case QgsGraphicsViewMouseHandles::ResizeLeftDown:
1090  return QSizeF( sceneMousePos.x(), sceneMousePos.y() - rect().height() );
1091 
1092  case MoveItem:
1093  case SelectItem:
1094  case NoAction:
1095  return QSizeF();
1096  }
1097 
1098  return QSizeF();
1099 }
1100 
1101 QRectF QgsGraphicsViewMouseHandles::selectionBounds() const
1102 {
1103  //calculate bounds of all currently selected items in mouse handle coordinate system
1104  const QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
1105  auto itemIter = selectedItems.constBegin();
1106 
1107  //start with handle bounds of first selected item
1108  QRectF bounds = mapFromItem( ( *itemIter ), itemRect( *itemIter ) ).boundingRect();
1109 
1110  //iterate through remaining items, expanding the bounds as required
1111  for ( ++itemIter; itemIter != selectedItems.constEnd(); ++itemIter )
1112  {
1113  bounds = bounds.united( mapFromItem( ( *itemIter ), itemRect( *itemIter ) ).boundingRect() );
1114  }
1115 
1116  return bounds;
1117 }
1118 
1119 void QgsGraphicsViewMouseHandles::relativeResizeRect( QRectF &rectToResize, const QRectF &boundsBefore, const QRectF &boundsAfter )
1120 {
1121  //linearly scale rectToResize relative to the scaling from boundsBefore to boundsAfter
1122  double left = relativePosition( rectToResize.left(), boundsBefore.left(), boundsBefore.right(), boundsAfter.left(), boundsAfter.right() );
1123  double right = relativePosition( rectToResize.right(), boundsBefore.left(), boundsBefore.right(), boundsAfter.left(), boundsAfter.right() );
1124  double top = relativePosition( rectToResize.top(), boundsBefore.top(), boundsBefore.bottom(), boundsAfter.top(), boundsAfter.bottom() );
1125  double bottom = relativePosition( rectToResize.bottom(), boundsBefore.top(), boundsBefore.bottom(), boundsAfter.top(), boundsAfter.bottom() );
1126 
1127  rectToResize.setRect( left, top, right - left, bottom - top );
1128 }
1129 
1130 double QgsGraphicsViewMouseHandles::relativePosition( double position, double beforeMin, double beforeMax, double afterMin, double afterMax )
1131 {
1132  //calculate parameters for linear scale between before and after ranges
1133  double m = ( afterMax - afterMin ) / ( beforeMax - beforeMin );
1134  double c = afterMin - ( beforeMin * m );
1135 
1136  //return linearly scaled position
1137  return m * position + c;
1138 }
1139 
Scoped object for saving and restoring a QPainter object's state.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:1246