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