QGIS API Documentation 3.99.0-Master (d270888f95f)
Loading...
Searching...
No Matches
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
20#include <limits>
21
22#include "qgis.h"
23#include "qgslayoututils.h"
24#include "qgsrendercontext.h"
25
26#include <QGraphicsSceneHoverEvent>
27#include <QGraphicsView>
28#include <QPainter>
29#include <QString>
30#include <QWidget>
31
32#include "moc_qgsgraphicsviewmousehandles.cpp"
33
34using namespace Qt::StringLiterals;
35
37
38QgsGraphicsViewMouseHandles::QgsGraphicsViewMouseHandles( QGraphicsView *view )
39 : QObject( nullptr )
40 , QGraphicsRectItem( nullptr )
41 , mView( view )
42{
43 //accept hover events, required for changing cursor to resize cursors
44 setAcceptHoverEvents( true );
45
46 //prepare rotation handle path
47 mRotationHandlePath.moveTo( 0, 14 );
48 mRotationHandlePath.lineTo( 6, 20 );
49 mRotationHandlePath.lineTo( 12, 14 );
50 mRotationHandlePath.arcTo( 8, 8, 12, 12, 180, -90 );
51 mRotationHandlePath.lineTo( 14, 12 );
52 mRotationHandlePath.lineTo( 20, 6 );
53 mRotationHandlePath.lineTo( 14, 0 );
54 mRotationHandlePath.arcTo( 4, 4, 20, 20, 90, 90 );
55 mRotationHandlePath.lineTo( 0, 14 );
56}
57
58void QgsGraphicsViewMouseHandles::setRotationEnabled( bool enable )
59{
60 if ( mRotationEnabled == enable )
61 {
62 return;
63 }
64
65 mRotationEnabled = enable;
66 update();
67}
68
69void QgsGraphicsViewMouseHandles::setCadMouseDigitizingModeEnabled( bool enable )
70{
71 if ( mCadMouseDigitizingMode == enable )
72 {
73 return;
74 }
75
76 mCadMouseDigitizingMode = enable;
77}
78
79void QgsGraphicsViewMouseHandles::paintInternal( QPainter *painter, bool showHandles, bool showStaticBoundingBoxes, bool showTemporaryBoundingBoxes, const QStyleOptionGraphicsItem *, QWidget * )
80{
81 if ( !showHandles )
82 {
83 return;
84 }
85
86 if ( showStaticBoundingBoxes )
87 {
88 //draw resize handles around bounds of entire selection
89 double rectHandlerSize = rectHandlerBorderTolerance();
90 drawHandles( painter, rectHandlerSize );
91 }
92
93 if ( showTemporaryBoundingBoxes && ( mIsResizing || mIsDragging || showStaticBoundingBoxes ) )
94 {
95 //draw dotted boxes around selected items
96 drawSelectedItemBounds( painter );
97 }
98}
99
100QRectF QgsGraphicsViewMouseHandles::storedItemRect( QGraphicsItem *item ) const
101{
102 return itemRect( item );
103}
104
105void QgsGraphicsViewMouseHandles::rotateItem( QGraphicsItem *, double, double, double )
106{
107 QgsDebugError( u"Rotation is not implemented for this class"_s );
108}
109
110void QgsGraphicsViewMouseHandles::previewItemMove( QGraphicsItem *, double, double )
111{
112}
113
114QRectF QgsGraphicsViewMouseHandles::previewSetItemRect( QGraphicsItem *, QRectF )
115{
116 return QRectF();
117}
118
119void QgsGraphicsViewMouseHandles::startMacroCommand( const QString & )
120{
121}
122
123void QgsGraphicsViewMouseHandles::endMacroCommand()
124{
125}
126
127void QgsGraphicsViewMouseHandles::endItemCommand( QGraphicsItem * )
128{
129}
130
131void QgsGraphicsViewMouseHandles::createItemCommand( QGraphicsItem * )
132{
133}
134
135QPointF QgsGraphicsViewMouseHandles::snapPoint( QPointF originalPoint, QgsGraphicsViewMouseHandles::SnapGuideMode, bool, bool )
136{
137 return originalPoint;
138}
139
140void QgsGraphicsViewMouseHandles::expandItemList( const QList<QGraphicsItem *> &items, QList<QGraphicsItem *> &collected ) const
141{
142 collected = items;
143}
144
145void QgsGraphicsViewMouseHandles::drawHandles( QPainter *painter, double rectHandlerSize )
146{
147 //blue, zero width cosmetic pen for outline
148 QPen handlePen = QPen( QColor( 55, 140, 195, 255 ) );
149 handlePen.setWidth( 0 );
150 painter->setPen( handlePen );
151
152 //draw box around entire selection bounds
153 painter->setBrush( Qt::NoBrush );
154 painter->drawRect( QRectF( 0, 0, rect().width(), rect().height() ) );
155
156 //draw resize handles, using filled white boxes
157 painter->setBrush( QColor( 255, 255, 255, 255 ) );
158 //top left
159 painter->drawRect( QRectF( 0, 0, rectHandlerSize, rectHandlerSize ) );
160 //mid top
161 painter->drawRect( QRectF( ( rect().width() - rectHandlerSize ) / 2, 0, rectHandlerSize, rectHandlerSize ) );
162 //top right
163 painter->drawRect( QRectF( rect().width() - rectHandlerSize, 0, rectHandlerSize, rectHandlerSize ) );
164 //mid left
165 painter->drawRect( QRectF( 0, ( rect().height() - rectHandlerSize ) / 2, rectHandlerSize, rectHandlerSize ) );
166 //mid right
167 painter->drawRect( QRectF( rect().width() - rectHandlerSize, ( rect().height() - rectHandlerSize ) / 2, rectHandlerSize, rectHandlerSize ) );
168 //bottom left
169 painter->drawRect( QRectF( 0, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) );
170 //mid bottom
171 painter->drawRect( QRectF( ( rect().width() - rectHandlerSize ) / 2, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) );
172 //bottom right
173 painter->drawRect( QRectF( rect().width() - rectHandlerSize, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) );
174
175 if ( isRotationEnabled() )
176 {
177 //draw rotate handles
178 const double scale = rectHandlerSize / mHandleSize;
179 const bool drawBottomRotationHandles = ( rectHandlerSize * 2 ) + ( mRotationHandleSize * scale * 2 ) < rect().height();
180 const bool drawRightRotationHandles = ( rectHandlerSize * 2 ) + ( mRotationHandleSize * scale * 2 ) < rect().width();
181 QTransform transform;
182
183 //top left
184 transform.reset();
185 transform.translate( rectHandlerSize, rectHandlerSize );
186 transform.scale( scale, scale );
187 painter->save();
188 painter->setTransform( transform, true );
189 painter->drawPath( mRotationHandlePath );
190 painter->restore();
191
192 //top right
193 if ( drawRightRotationHandles )
194 {
195 transform.reset();
196 transform.translate( rect().width() - rectHandlerSize, rectHandlerSize );
197 transform.rotate( 90 );
198 transform.scale( scale, scale );
199 painter->save();
200 painter->setTransform( transform, true );
201 painter->drawPath( mRotationHandlePath );
202 painter->restore();
203 }
204
205 if ( drawBottomRotationHandles )
206 {
207 //bottom left
208 transform.reset();
209 transform.translate( rectHandlerSize, rect().height() - rectHandlerSize );
210 transform.rotate( 270 );
211 transform.scale( scale, scale );
212 painter->save();
213 painter->setTransform( transform, true );
214 painter->drawPath( mRotationHandlePath );
215 painter->restore();
216 }
217
218 if ( drawBottomRotationHandles && drawRightRotationHandles )
219 {
220 //bottom right
221 transform.reset();
222 transform.translate( rect().width() - rectHandlerSize, rect().height() - rectHandlerSize );
223 transform.rotate( 180 );
224 transform.scale( scale, scale );
225 painter->save();
226 painter->setTransform( transform, true );
227 painter->drawPath( mRotationHandlePath );
228 painter->restore();
229 }
230 }
231}
232
233void QgsGraphicsViewMouseHandles::drawSelectedItemBounds( QPainter *painter )
234{
235 //draw dotted border around selected items to give visual feedback which items are selected
236 const QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
237 if ( selectedItems.isEmpty() )
238 {
239 return;
240 }
241
242 QList<QGraphicsItem *> itemsToDraw;
243 expandItemList( selectedItems, itemsToDraw );
244
245 if ( itemsToDraw.size() <= 1 )
246 {
247 // Single item selected. The items bounds are drawn by the MouseHandles itself.
248 return;
249 }
250
251 //use difference mode so that they are visible regardless of item colors
252 QgsScopedQPainterState painterState( painter );
253 painter->setCompositionMode( QPainter::CompositionMode_Difference );
254
255 // use a grey dashed pen - in difference mode this should always be visible
256 QPen selectedItemPen = QPen( QColor( 144, 144, 144, 255 ) );
257 selectedItemPen.setStyle( Qt::DashLine );
258 selectedItemPen.setWidth( 0 );
259 painter->setPen( selectedItemPen );
260 painter->setBrush( Qt::NoBrush );
261
262 for ( QGraphicsItem *item : std::as_const( itemsToDraw ) )
263 {
264 //get bounds of selected item
265 QPolygonF itemBounds;
266 if ( isDragging() && !itemIsLocked( item ) )
267 {
268 //if currently dragging, draw selected item bounds relative to current mouse position
269 //first, get bounds of current item in scene coordinates
270 QPolygonF itemSceneBounds = item->mapToScene( itemRect( item ) );
271 //now, translate it by the current movement amount
272 //IMPORTANT - this is done in scene coordinates, since we don't want any rotation/non-translation transforms to affect the movement
273 itemSceneBounds.translate( transform().dx(), transform().dy() );
274 //finally, remap it to the mouse handle item's coordinate system so it's ready for drawing
275 itemBounds = mapFromScene( itemSceneBounds );
276 }
277 else if ( isResizing() && !itemIsLocked( item ) )
278 {
279 //if currently resizing, calculate relative resize of this item
280 //get item bounds in mouse handle item's coordinate system
281 QRectF thisItemRect = mapRectFromItem( item, itemRect( item ) );
282 //now, resize it relative to the current resized dimensions of the mouse handles
283 relativeResizeRect( thisItemRect, QRectF( -mResizeMoveX, -mResizeMoveY, mBeginHandleWidth, mBeginHandleHeight ), mResizeRect );
284 itemBounds = QPolygonF( thisItemRect );
285 }
286 else if ( isRotating() && !itemIsLocked( item ) )
287 {
288 const QPolygonF itemSceneBounds = item->mapToScene( itemRect( item ) );
289 const QPointF rotationCenter = sceneTransform().map( rect().center() );
290
291 QTransform transform;
292 transform.translate( rotationCenter.x(), rotationCenter.y() );
293 transform.rotate( mRotationDelta );
294 transform.translate( -rotationCenter.x(), -rotationCenter.y() );
295 itemBounds = mapFromScene( transform.map( itemSceneBounds ) );
296 }
297 else
298 {
299 // not resizing or moving, so just map the item's bounds to the mouse handle item's coordinate system
300 itemBounds = item->mapToItem( this, itemRect( item ) );
301 }
302
303 // drawPolygon causes issues on windows - corners of path may be missing resulting in triangles being drawn
304 // instead of rectangles! (Same cause as #13343)
305 QPainterPath path;
306 path.addPolygon( itemBounds );
307 painter->drawPath( path );
308 }
309}
310
311double QgsGraphicsViewMouseHandles::rectHandlerBorderTolerance() const
312{
313 if ( !mView )
314 return 0;
315
316 //calculate size for resize handles
317 //get view scale factor
318 double viewScaleFactor = mView->transform().m11();
319
320 //size of handle boxes depends on zoom level in layout view
321 double rectHandlerSize = mHandleSize / viewScaleFactor;
322
323 //make sure the boxes don't get too large
324 if ( rectHandlerSize > ( rect().width() / 3 ) )
325 {
326 rectHandlerSize = rect().width() / 3;
327 }
328 if ( rectHandlerSize > ( rect().height() / 3 ) )
329 {
330 rectHandlerSize = rect().height() / 3;
331 }
332 return rectHandlerSize;
333}
334
335Qt::CursorShape QgsGraphicsViewMouseHandles::cursorForPosition( QPointF itemCoordPos )
336{
337 Qgis::MouseHandlesAction mouseAction = mouseActionForPosition( itemCoordPos );
338 double normalizedRotation = std::fmod( rotation(), 360 );
339 if ( normalizedRotation < 0 )
340 {
341 normalizedRotation += 360;
342 }
343 switch ( mouseAction )
344 {
346 return Qt::ForbiddenCursor;
348 return Qt::SizeAllCursor;
351 //account for rotation
352 if ( ( normalizedRotation <= 22.5 || normalizedRotation >= 337.5 ) || ( normalizedRotation >= 157.5 && normalizedRotation <= 202.5 ) )
353 {
354 return Qt::SizeVerCursor;
355 }
356 else if ( ( normalizedRotation >= 22.5 && normalizedRotation <= 67.5 ) || ( normalizedRotation >= 202.5 && normalizedRotation <= 247.5 ) )
357 {
358 return Qt::SizeBDiagCursor;
359 }
360 else if ( ( normalizedRotation >= 67.5 && normalizedRotation <= 112.5 ) || ( normalizedRotation >= 247.5 && normalizedRotation <= 292.5 ) )
361 {
362 return Qt::SizeHorCursor;
363 }
364 else
365 {
366 return Qt::SizeFDiagCursor;
367 }
370 //account for rotation
371 if ( ( normalizedRotation <= 22.5 || normalizedRotation >= 337.5 ) || ( normalizedRotation >= 157.5 && normalizedRotation <= 202.5 ) )
372 {
373 return Qt::SizeHorCursor;
374 }
375 else if ( ( normalizedRotation >= 22.5 && normalizedRotation <= 67.5 ) || ( normalizedRotation >= 202.5 && normalizedRotation <= 247.5 ) )
376 {
377 return Qt::SizeFDiagCursor;
378 }
379 else if ( ( normalizedRotation >= 67.5 && normalizedRotation <= 112.5 ) || ( normalizedRotation >= 247.5 && normalizedRotation <= 292.5 ) )
380 {
381 return Qt::SizeVerCursor;
382 }
383 else
384 {
385 return Qt::SizeBDiagCursor;
386 }
387
390 //account for rotation
391 if ( ( normalizedRotation <= 22.5 || normalizedRotation >= 337.5 ) || ( normalizedRotation >= 157.5 && normalizedRotation <= 202.5 ) )
392 {
393 return Qt::SizeFDiagCursor;
394 }
395 else if ( ( normalizedRotation >= 22.5 && normalizedRotation <= 67.5 ) || ( normalizedRotation >= 202.5 && normalizedRotation <= 247.5 ) )
396 {
397 return Qt::SizeVerCursor;
398 }
399 else if ( ( normalizedRotation >= 67.5 && normalizedRotation <= 112.5 ) || ( normalizedRotation >= 247.5 && normalizedRotation <= 292.5 ) )
400 {
401 return Qt::SizeBDiagCursor;
402 }
403 else
404 {
405 return Qt::SizeHorCursor;
406 }
409 //account for rotation
410 if ( ( normalizedRotation <= 22.5 || normalizedRotation >= 337.5 ) || ( normalizedRotation >= 157.5 && normalizedRotation <= 202.5 ) )
411 {
412 return Qt::SizeBDiagCursor;
413 }
414 else if ( ( normalizedRotation >= 22.5 && normalizedRotation <= 67.5 ) || ( normalizedRotation >= 202.5 && normalizedRotation <= 247.5 ) )
415 {
416 return Qt::SizeHorCursor;
417 }
418 else if ( ( normalizedRotation >= 67.5 && normalizedRotation <= 112.5 ) || ( normalizedRotation >= 247.5 && normalizedRotation <= 292.5 ) )
419 {
420 return Qt::SizeFDiagCursor;
421 }
422 else
423 {
424 return Qt::SizeVerCursor;
425 }
427 return Qt::ArrowCursor;
428
433 return Qt::PointingHandCursor;
434 }
435
436 return Qt::ArrowCursor;
437}
438
439Qgis::MouseHandlesAction QgsGraphicsViewMouseHandles::mouseActionForPosition( QPointF itemCoordPos )
440{
441 bool nearLeftBorder = false;
442 bool nearRightBorder = false;
443 bool nearLowerBorder = false;
444 bool nearUpperBorder = false;
445
446 bool nearLeftInner = false;
447 bool nearRightInner = false;
448 bool nearLowerInner = false;
449 bool nearUpperInner = false;
450
451 bool withinWidth = false;
452 bool withinHeight = false;
453
454 if ( itemCoordPos.x() >= 0 && itemCoordPos.x() <= rect().width() )
455 {
456 withinWidth = true;
457 }
458 if ( itemCoordPos.y() >= 0 && itemCoordPos.y() <= rect().height() )
459 {
460 withinHeight = true;
461 }
462
463 double borderTolerance = rectHandlerBorderTolerance();
464 double innerTolerance = mRotationHandleSize * borderTolerance / mHandleSize;
465
466 if ( itemCoordPos.x() >= 0 && itemCoordPos.x() < borderTolerance )
467 {
468 nearLeftBorder = true;
469 }
470 else if ( isRotationEnabled() && itemCoordPos.x() >= borderTolerance && itemCoordPos.x() < ( borderTolerance + innerTolerance ) )
471 {
472 nearLeftInner = true;
473 }
474 if ( itemCoordPos.y() >= 0 && itemCoordPos.y() < borderTolerance )
475 {
476 nearUpperBorder = true;
477 }
478 else if ( isRotationEnabled() && itemCoordPos.y() >= borderTolerance && itemCoordPos.y() < ( borderTolerance + innerTolerance ) )
479 {
480 nearUpperInner = true;
481 }
482 if ( itemCoordPos.x() <= rect().width() && itemCoordPos.x() > ( rect().width() - borderTolerance ) )
483 {
484 nearRightBorder = true;
485 }
486 else if ( isRotationEnabled() && itemCoordPos.x() <= ( rect().width() - borderTolerance ) && itemCoordPos.x() > ( rect().width() - borderTolerance - innerTolerance ) )
487 {
488 nearRightInner = true;
489 }
490 if ( itemCoordPos.y() <= rect().height() && itemCoordPos.y() > ( rect().height() - borderTolerance ) )
491 {
492 nearLowerBorder = true;
493 }
494 else if ( isRotationEnabled() && itemCoordPos.y() <= ( rect().height() - borderTolerance ) && itemCoordPos.y() > ( rect().height() - borderTolerance - innerTolerance ) )
495 {
496 nearLowerInner = true;
497 }
498
499 if ( nearLeftBorder && nearUpperBorder )
500 {
502 }
503 else if ( nearLeftBorder && nearLowerBorder )
504 {
506 }
507 else if ( nearRightBorder && nearUpperBorder )
508 {
510 }
511 else if ( nearRightBorder && nearLowerBorder )
512 {
514 }
515 else if ( nearLeftBorder && withinHeight )
516 {
518 }
519 else if ( nearRightBorder && withinHeight )
520 {
522 }
523 else if ( nearUpperBorder && withinWidth )
524 {
526 }
527 else if ( nearLowerBorder && withinWidth )
528 {
530 }
531 else if ( nearLeftInner && nearUpperInner )
532 {
534 }
535 else if ( nearRightInner && nearUpperInner )
536 {
538 }
539 else if ( nearLeftInner && nearLowerInner )
540 {
542 }
543 else if ( nearRightInner && nearLowerInner )
544 {
546 }
547
548 //find out if cursor position is over a selected item
549 QPointF scenePoint = mapToScene( itemCoordPos );
550 const QList<QGraphicsItem *> itemsAtCursorPos = sceneItemsAtPoint( scenePoint );
551 if ( itemsAtCursorPos.isEmpty() )
552 {
553 //no items at cursor position
555 }
556 for ( QGraphicsItem *graphicsItem : itemsAtCursorPos )
557 {
558 if ( graphicsItem && graphicsItem->isSelected() )
559 {
560 //cursor is over a selected layout item
562 }
563 }
564
565 //default
567}
568
569Qgis::MouseHandlesAction QgsGraphicsViewMouseHandles::mouseActionForScenePos( QPointF sceneCoordPos )
570{
571 // convert sceneCoordPos to item coordinates
572 QPointF itemPos = mapFromScene( sceneCoordPos );
573 return mouseActionForPosition( itemPos );
574}
575
576bool QgsGraphicsViewMouseHandles::shouldBlockEvent( QInputEvent * ) const
577{
578 return mIsDragging || mIsResizing || mIsRotating;
579}
580
581void QgsGraphicsViewMouseHandles::startMove( QPointF sceneCoordPos )
582{
583 //save current cursor position
584 mMouseMoveStartPos = sceneCoordPos;
585 //save current item geometry
586 mBeginMouseEventPos = sceneCoordPos;
587 mBeginHandlePos = scenePos();
588 mBeginHandleWidth = rect().width();
589 mBeginHandleHeight = rect().height();
590 mCurrentMouseMoveAction = Qgis::MouseHandlesAction::MoveItem;
591 mIsDragging = true;
592 hideAlignItems();
593
594 // Explicitly call grabMouse to ensure the mouse handles receive the subsequent mouse move events.
595 if ( mView->scene()->mouseGrabberItem() != this )
596 {
597 grabMouse();
598 }
599}
600
601void QgsGraphicsViewMouseHandles::selectedItemSizeChanged()
602{
603 if ( !isDragging() && !isResizing() )
604 {
605 //only required for non-mouse initiated size changes
606 updateHandles();
607 }
608}
609
610void QgsGraphicsViewMouseHandles::selectedItemRotationChanged()
611{
612 if ( !isDragging() && !isResizing() )
613 {
614 //only required for non-mouse initiated rotation changes
615 updateHandles();
616 }
617}
618
619void QgsGraphicsViewMouseHandles::hoverMoveEvent( QGraphicsSceneHoverEvent *event )
620{
621 setViewportCursor( cursorForPosition( event->pos() ) );
622}
623
624void QgsGraphicsViewMouseHandles::hoverLeaveEvent( QGraphicsSceneHoverEvent *event )
625{
626 Q_UNUSED( event )
627 setViewportCursor( Qt::ArrowCursor );
628}
629
630void QgsGraphicsViewMouseHandles::mousePressEvent( QGraphicsSceneMouseEvent *event )
631{
632 if ( event->button() != Qt::LeftButton )
633 {
634 event->ignore();
635 return;
636 }
637
638 if ( mCadMouseDigitizingMode && ( mIsDragging || mIsResizing || mIsRotating ) )
639 {
640 return;
641 }
642
643 //save current cursor position
644 mMouseMoveStartPos = event->lastScenePos();
645 //save current item geometry
646 mBeginMouseEventPos = event->lastScenePos();
647 mBeginHandlePos = scenePos();
648 mBeginHandleWidth = rect().width();
649 mBeginHandleHeight = rect().height();
650 //type of mouse move action
651 mCurrentMouseMoveAction = mouseActionForPosition( event->pos() );
652
653 hideAlignItems();
654
655 switch ( mCurrentMouseMoveAction )
656 {
658 //moving items
659 mIsDragging = true;
660 break;
661
670 //resizing items
671 mIsResizing = true;
672 mResizeRect = QRectF( 0, 0, mBeginHandleWidth, mBeginHandleHeight );
673 mResizeMoveX = 0;
674 mResizeMoveY = 0;
675 mCursorOffset = calcCursorEdgeOffset( mMouseMoveStartPos );
676 break;
677
682 mIsRotating = true;
683 mRotationCenter = sceneTransform().map( rect().center() );
684 mRotationBegin = std::atan2( mMouseMoveStartPos.y() - mRotationCenter.y(), mMouseMoveStartPos.x() - mRotationCenter.x() ) * 180 / M_PI;
685 mRotationCurrent = 0.0;
686 break;
687
690 break;
691 }
692
693 if ( mCadMouseDigitizingMode && mIsDragging )
694 {
695 mIsDragStarting = true;
696 }
697}
698
699void QgsGraphicsViewMouseHandles::resetStatusBar()
700{
701 const QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
702 int selectedCount = selectedItems.size();
703 if ( selectedCount )
704 {
705 //set status bar message to count of selected items
706 showStatusMessage( tr( "%n item(s) selected", nullptr, selectedCount ) );
707 }
708 else
709 {
710 //clear status bar message
711 showStatusMessage( QString() );
712 }
713}
714
715void QgsGraphicsViewMouseHandles::mouseMoveEvent( QGraphicsSceneMouseEvent *event )
716{
717 if ( isDragging() )
718 {
719 //currently dragging a selection
720 //if shift depressed, constrain movement to horizontal/vertical
721 //if control depressed, ignore snapping
722 dragMouseMove( event->lastScenePos(), event->modifiers() & Qt::ShiftModifier, event->modifiers() & Qt::ControlModifier );
723 }
724 else if ( isResizing() )
725 {
726 //currently resizing a selection
727 //lock aspect ratio if shift depressed
728 //resize from center if alt depressed
729 resizeMouseMove( event->lastScenePos(), event->modifiers() & Qt::ShiftModifier, event->modifiers() & Qt::AltModifier );
730 }
731 else if ( isRotating() )
732 {
733 //currently rotating a selection
734 //snap to common angles if ctrl is pressed
735 rotateMouseMove( event->lastScenePos(), event->modifiers() & Qt::ControlModifier );
736 }
737}
738
739void QgsGraphicsViewMouseHandles::mouseReleaseEvent( QGraphicsSceneMouseEvent *event )
740{
741 if ( event->button() != Qt::LeftButton )
742 {
743 event->ignore();
744 return;
745 }
746
747 if ( mDoubleClickInProgress )
748 {
749 mDoubleClickInProgress = false;
750 event->accept();
751 return;
752 }
753
754 if ( mIsDragStarting )
755 {
756 mIsDragStarting = false;
757 return;
758 }
759
760 // Mouse may have been grabbed from the QgsLayoutViewSelectTool, so we need to release it explicitly
761 // otherwise, hover events will not be received
762 if ( mView->scene()->mouseGrabberItem() == this )
763 {
764 ungrabMouse();
765 }
766
767 QPointF mouseMoveStopPoint = event->lastScenePos();
768 double diffX = mouseMoveStopPoint.x() - mMouseMoveStartPos.x();
769 double diffY = mouseMoveStopPoint.y() - mMouseMoveStartPos.y();
770
771 const bool isClick = std::fabs( diffX ) < std::numeric_limits<double>::min() && std::fabs( diffY ) < std::numeric_limits<double>::min();
772 if ( isClick )
773 {
774 mIsDragging = false;
775 mIsResizing = false;
776 mIsRotating = false;
777 update();
778 hideAlignItems();
779 return;
780 }
781
782 if ( mIsDragging )
783 {
784 //move selected items
785 startMacroCommand( tr( "Move Items" ) );
786
787 QPointF mEndHandleMovePos = scenePos();
788
789 double deltaX = mEndHandleMovePos.x() - mBeginHandlePos.x();
790 double deltaY = mEndHandleMovePos.y() - mBeginHandlePos.y();
791
792 //move all selected items
793 const QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
794 for ( QGraphicsItem *item : selectedItems )
795 {
796 if ( itemIsLocked( item ) || ( item->flags() & QGraphicsItem::ItemIsSelectable ) == 0 || itemIsGroupMember( item ) )
797 {
798 //don't move locked items, or grouped items (group takes care of that)
799 continue;
800 }
801
802 createItemCommand( item );
803 moveItem( item, deltaX, deltaY );
804 endItemCommand( item );
805 }
806 endMacroCommand();
807
808 mIsDragging = false;
809 }
810 else if ( mIsResizing )
811 {
812 //resize selected items
813 startMacroCommand( tr( "Resize Items" ) );
814
815 //resize all selected items
816 const QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
817 for ( QGraphicsItem *item : selectedItems )
818 {
819 if ( itemIsLocked( item ) || ( item->flags() & QGraphicsItem::ItemIsSelectable ) == 0 )
820 {
821 //don't resize locked items or deselectable items (e.g., items which make up an item group)
822 continue;
823 }
824 createItemCommand( item );
825
826 QRectF thisItemRect;
827 if ( selectedItems.size() == 1 )
828 {
829 //only a single item is selected, so set its size to the final resized mouse handle size
830 thisItemRect = mResizeRect;
831 }
832 else
833 {
834 //multiple items selected, so each needs to be scaled relatively to the final size of the mouse handles
835 thisItemRect = mapRectFromItem( item, itemRect( item ) );
836 relativeResizeRect( thisItemRect, QRectF( -mResizeMoveX, -mResizeMoveY, mBeginHandleWidth, mBeginHandleHeight ), mResizeRect );
837 }
838
839 thisItemRect = thisItemRect.normalized();
840 QPointF newPos = mapToScene( thisItemRect.topLeft() );
841 thisItemRect.moveTopLeft( newPos );
842 setItemRect( item, thisItemRect );
843
844 endItemCommand( item );
845 }
846 endMacroCommand();
847
848 mIsResizing = false;
849 }
850 else if ( mIsRotating )
851 {
852 const QPointF itemRotationCenter = sceneTransform().map( rect().center() );
853
854 //move selected items
855 startMacroCommand( tr( "Rotate Items" ) );
856
857 //move all selected items
858 const QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
859 for ( QGraphicsItem *item : selectedItems )
860 {
861 if ( itemIsLocked( item ) || ( item->flags() & QGraphicsItem::ItemIsSelectable ) == 0 || itemIsGroupMember( item ) )
862 {
863 //don't move locked items, or grouped items (group takes care of that)
864 continue;
865 }
866
867 const QPointF itemCenter = item->mapToScene( itemRect( item ) ).boundingRect().center();
868
869 QTransform transform;
870 transform.translate( itemRotationCenter.x(), itemRotationCenter.y() );
871 transform.rotate( mRotationDelta );
872 transform.translate( -itemRotationCenter.x(), -itemRotationCenter.y() );
873 const QPointF rotatedItemCenter = transform.map( itemCenter );
874
875 createItemCommand( item );
876 rotateItem( item, mRotationDelta, rotatedItemCenter.x() - itemCenter.x(), rotatedItemCenter.y() - itemCenter.y() );
877 endItemCommand( item );
878 }
879 endMacroCommand();
880
881 mIsRotating = false;
882 }
883
884 hideAlignItems();
885
886 //reset default action
887 mCurrentMouseMoveAction = Qgis::MouseHandlesAction::MoveItem;
888 //redraw handles
889 resetTransform();
890 update();
891 updateHandles();
892 //reset status bar message
893 resetStatusBar();
894}
895
896bool QgsGraphicsViewMouseHandles::selectionRotation( double &rotation ) const
897{
898 //check if all selected items have same rotation
899 QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
900 auto itemIter = selectedItems.constBegin();
901
902 //start with rotation of first selected item
903 double firstItemRotation = ( *itemIter )->rotation();
904
905 //iterate through remaining items, checking if they have same rotation
906 for ( ++itemIter; itemIter != selectedItems.constEnd(); ++itemIter )
907 {
908 if ( !qgsDoubleNear( ( *itemIter )->rotation(), firstItemRotation ) )
909 {
910 //item has a different rotation, so return false
911 return false;
912 }
913 }
914
915 //all items have the same rotation, so set the rotation variable and return true
916 rotation = firstItemRotation;
917 return true;
918}
919
920void QgsGraphicsViewMouseHandles::updateHandles()
921{
922 //recalculate size and position of handle item
923
924 //first check to see if any items are selected
925 QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
926 if ( !selectedItems.isEmpty() )
927 {
928 //one or more items are selected, get bounds of all selected items
929
930 //update rotation of handle object
931 double rotation;
932 if ( selectionRotation( rotation ) )
933 {
934 //all items share a common rotation value, so we rotate the mouse handles to match
935 setRotation( rotation );
936 }
937 else
938 {
939 //items have varying rotation values - we can't rotate the mouse handles to match
940 setRotation( 0 );
941 }
942
943 //get bounds of all selected items
944 QRectF newHandleBounds = selectionBounds();
945
946 //update size and position of handle object
947 setRect( 0, 0, newHandleBounds.width(), newHandleBounds.height() );
948 setPos( mapToScene( newHandleBounds.topLeft() ) );
949
950 show();
951 }
952 else
953 {
954 //no items selected, hide handles
955 hide();
956 }
957
958 //force redraw
959 update();
960}
961
962void QgsGraphicsViewMouseHandles::rotateMouseMove( QPointF currentPosition, bool snapToCommonAngles )
963{
964 if ( !scene() )
965 {
966 return;
967 }
968
969 mRotationCurrent = std::atan2( currentPosition.y() - mRotationCenter.y(), currentPosition.x() - mRotationCenter.x() ) * 180 / M_PI;
970 mRotationDelta = mRotationCurrent - mRotationBegin;
971 if ( snapToCommonAngles )
972 {
973 mRotationDelta = QgsLayoutUtils::snappedAngle( mRotationDelta );
974 }
975
976 const double itemRotationRadian = rotation() * M_PI / 180;
977 const double deltaX = ( rect().width() / 2 ) * cos( itemRotationRadian ) - ( rect().height() / 2 ) * sin( itemRotationRadian );
978 const double deltaY = ( rect().width() / 2 ) * sin( itemRotationRadian ) + ( rect().height() / 2 ) * cos( itemRotationRadian );
979
980 QTransform rotateTransform;
981 rotateTransform.translate( deltaX, deltaY );
982 rotateTransform.rotate( mRotationDelta );
983 rotateTransform.translate( -deltaX, -deltaY );
984 setTransform( rotateTransform );
985
986 //show current selection rotation in status bar
987 showStatusMessage( tr( "rotation: %1°" ).arg( QString::number( mRotationDelta, 'f', 2 ) ) );
988
989 return;
990}
991
992void QgsGraphicsViewMouseHandles::dragMouseMove( QPointF currentPosition, bool lockMovement, bool preventSnap )
993{
994 if ( !scene() )
995 {
996 return;
997 }
998
999 //calculate total amount of mouse movement since drag began
1000 double moveX = currentPosition.x() - mBeginMouseEventPos.x();
1001 double moveY = currentPosition.y() - mBeginMouseEventPos.y();
1002
1003 //find target position before snapping (in scene coordinates)
1004 QPointF upperLeftPoint( mBeginHandlePos.x() + moveX, mBeginHandlePos.y() + moveY );
1005
1006 QPointF snappedLeftPoint;
1007
1008 //no snapping for rotated items for now
1009 if ( !preventSnap && qgsDoubleNear( rotation(), 0.0 ) )
1010 {
1011 //snap to grid and guides
1012 snappedLeftPoint = snapPoint( upperLeftPoint, Item );
1013 }
1014 else
1015 {
1016 //no snapping
1017 snappedLeftPoint = upperLeftPoint;
1018 hideAlignItems();
1019 }
1020
1021 //calculate total shift for item from beginning of drag operation to current position
1022 double moveRectX = snappedLeftPoint.x() - mBeginHandlePos.x();
1023 double moveRectY = snappedLeftPoint.y() - mBeginHandlePos.y();
1024
1025 if ( lockMovement )
1026 {
1027 //constrained (shift) moving should lock to horizontal/vertical movement
1028 //reset the smaller of the x/y movements
1029 if ( std::fabs( moveRectX ) <= std::fabs( moveRectY ) )
1030 {
1031 moveRectX = 0;
1032 }
1033 else
1034 {
1035 moveRectY = 0;
1036 }
1037 }
1038
1039 //shift handle item to new position
1040 QTransform moveTransform;
1041 moveTransform.translate( moveRectX, moveRectY );
1042 setTransform( moveTransform );
1043
1044 const QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
1045 for ( QGraphicsItem *item : selectedItems )
1046 {
1047 previewItemMove( item, moveRectX, moveRectY );
1048 }
1049 //show current displacement of selection in status bar
1050 showStatusMessage( tr( "dx: %1 mm dy: %2 mm" ).arg( moveRectX ).arg( moveRectY ) );
1051}
1052
1053void QgsGraphicsViewMouseHandles::resizeMouseMove( QPointF currentPosition, bool lockRatio, bool fromCenter )
1054{
1055 if ( !scene() )
1056 {
1057 return;
1058 }
1059
1060 double mx = 0.0, my = 0.0, rx = 0.0, ry = 0.0;
1061
1062 QPointF beginMousePos;
1063 QPointF finalPosition;
1064 if ( qgsDoubleNear( rotation(), 0.0 ) )
1065 {
1066 //snapping only occurs if handles are not rotated for now
1067
1068 bool snapVertical = mCurrentMouseMoveAction == Qgis::MouseHandlesAction::ResizeLeft || mCurrentMouseMoveAction == Qgis::MouseHandlesAction::ResizeRight || mCurrentMouseMoveAction == Qgis::MouseHandlesAction::ResizeLeftUp || mCurrentMouseMoveAction == Qgis::MouseHandlesAction::ResizeRightUp || mCurrentMouseMoveAction == Qgis::MouseHandlesAction::ResizeLeftDown || mCurrentMouseMoveAction == Qgis::MouseHandlesAction::ResizeRightDown;
1069
1070 bool snapHorizontal = mCurrentMouseMoveAction == Qgis::MouseHandlesAction::ResizeUp || mCurrentMouseMoveAction == Qgis::MouseHandlesAction::ResizeDown || mCurrentMouseMoveAction == Qgis::MouseHandlesAction::ResizeLeftUp || mCurrentMouseMoveAction == Qgis::MouseHandlesAction::ResizeRightUp || mCurrentMouseMoveAction == Qgis::MouseHandlesAction::ResizeLeftDown || mCurrentMouseMoveAction == Qgis::MouseHandlesAction::ResizeRightDown;
1071
1072 //subtract cursor edge offset from begin mouse event and current cursor position, so that snapping occurs to edge of mouse handles
1073 //rather then cursor position
1074 beginMousePos = mapFromScene( QPointF( mBeginMouseEventPos.x() - mCursorOffset.width(), mBeginMouseEventPos.y() - mCursorOffset.height() ) );
1075 QPointF snappedPosition = snapPoint( QPointF( currentPosition.x() - mCursorOffset.width(), currentPosition.y() - mCursorOffset.height() ), Point, snapHorizontal, snapVertical );
1076 finalPosition = mapFromScene( snappedPosition );
1077 }
1078 else
1079 {
1080 //no snapping for rotated items for now
1081 beginMousePos = mapFromScene( mBeginMouseEventPos );
1082 finalPosition = mapFromScene( currentPosition );
1083 }
1084
1085 double diffX = finalPosition.x() - beginMousePos.x();
1086 double diffY = finalPosition.y() - beginMousePos.y();
1087
1088 double ratio = 0;
1089 if ( lockRatio && !qgsDoubleNear( mBeginHandleHeight, 0.0 ) )
1090 {
1091 ratio = mBeginHandleWidth / mBeginHandleHeight;
1092 }
1093
1094 switch ( mCurrentMouseMoveAction )
1095 {
1096 //vertical resize
1098 {
1099 if ( ratio )
1100 {
1101 diffX = ( ( mBeginHandleHeight - diffY ) * ratio ) - mBeginHandleWidth;
1102 mx = -diffX / 2;
1103 my = diffY;
1104 rx = diffX;
1105 ry = -diffY;
1106 }
1107 else
1108 {
1109 mx = 0;
1110 my = diffY;
1111 rx = 0;
1112 ry = -diffY;
1113 }
1114 break;
1115 }
1116
1118 {
1119 if ( ratio )
1120 {
1121 diffX = ( ( mBeginHandleHeight + diffY ) * ratio ) - mBeginHandleWidth;
1122 mx = -diffX / 2;
1123 my = 0;
1124 rx = diffX;
1125 ry = diffY;
1126 }
1127 else
1128 {
1129 mx = 0;
1130 my = 0;
1131 rx = 0;
1132 ry = diffY;
1133 }
1134 break;
1135 }
1136
1137 //horizontal resize
1139 {
1140 if ( ratio )
1141 {
1142 diffY = ( ( mBeginHandleWidth - diffX ) / ratio ) - mBeginHandleHeight;
1143 mx = diffX;
1144 my = -diffY / 2;
1145 rx = -diffX;
1146 ry = diffY;
1147 }
1148 else
1149 {
1150 mx = diffX, my = 0;
1151 rx = -diffX;
1152 ry = 0;
1153 }
1154 break;
1155 }
1156
1158 {
1159 if ( ratio )
1160 {
1161 diffY = ( ( mBeginHandleWidth + diffX ) / ratio ) - mBeginHandleHeight;
1162 mx = 0;
1163 my = -diffY / 2;
1164 rx = diffX;
1165 ry = diffY;
1166 }
1167 else
1168 {
1169 mx = 0;
1170 my = 0;
1171 rx = diffX, ry = 0;
1172 }
1173 break;
1174 }
1175
1176 //diagonal resize
1178 {
1179 if ( ratio )
1180 {
1181 //ratio locked resize
1182 if ( ( mBeginHandleWidth - diffX ) / ( mBeginHandleHeight - diffY ) > ratio )
1183 {
1184 diffX = mBeginHandleWidth - ( ( mBeginHandleHeight - diffY ) * ratio );
1185 }
1186 else
1187 {
1188 diffY = mBeginHandleHeight - ( ( mBeginHandleWidth - diffX ) / ratio );
1189 }
1190 }
1191 mx = diffX, my = diffY;
1192 rx = -diffX;
1193 ry = -diffY;
1194 break;
1195 }
1196
1198 {
1199 if ( ratio )
1200 {
1201 //ratio locked resize
1202 if ( ( mBeginHandleWidth + diffX ) / ( mBeginHandleHeight + diffY ) > ratio )
1203 {
1204 diffX = ( ( mBeginHandleHeight + diffY ) * ratio ) - mBeginHandleWidth;
1205 }
1206 else
1207 {
1208 diffY = ( ( mBeginHandleWidth + diffX ) / ratio ) - mBeginHandleHeight;
1209 }
1210 }
1211 mx = 0;
1212 my = 0;
1213 rx = diffX, ry = diffY;
1214 break;
1215 }
1216
1218 {
1219 if ( ratio )
1220 {
1221 //ratio locked resize
1222 if ( ( mBeginHandleWidth + diffX ) / ( mBeginHandleHeight - diffY ) > ratio )
1223 {
1224 diffX = ( ( mBeginHandleHeight - diffY ) * ratio ) - mBeginHandleWidth;
1225 }
1226 else
1227 {
1228 diffY = mBeginHandleHeight - ( ( mBeginHandleWidth + diffX ) / ratio );
1229 }
1230 }
1231 mx = 0;
1232 my = diffY, rx = diffX, ry = -diffY;
1233 break;
1234 }
1235
1237 {
1238 if ( ratio )
1239 {
1240 //ratio locked resize
1241 if ( ( mBeginHandleWidth - diffX ) / ( mBeginHandleHeight + diffY ) > ratio )
1242 {
1243 diffX = mBeginHandleWidth - ( ( mBeginHandleHeight + diffY ) * ratio );
1244 }
1245 else
1246 {
1247 diffY = ( ( mBeginHandleWidth - diffX ) / ratio ) - mBeginHandleHeight;
1248 }
1249 }
1250 mx = diffX, my = 0;
1251 rx = -diffX;
1252 ry = diffY;
1253 break;
1254 }
1255
1263 break;
1264 }
1265
1266 //resizing from center of objects?
1267 if ( fromCenter )
1268 {
1269 my = -ry;
1270 mx = -rx;
1271 ry = 2 * ry;
1272 rx = 2 * rx;
1273 }
1274
1275 //update selection handle rectangle
1276
1277 //make sure selection handle size rectangle is normalized (ie, left coord < right coord)
1278 mResizeMoveX = mBeginHandleWidth + rx > 0 ? mx : mx + mBeginHandleWidth + rx;
1279 mResizeMoveY = mBeginHandleHeight + ry > 0 ? my : my + mBeginHandleHeight + ry;
1280
1281 //calculate movement in scene coordinates
1282 QLineF translateLine = QLineF( 0, 0, mResizeMoveX, mResizeMoveY );
1283 translateLine.setAngle( translateLine.angle() - rotation() );
1284 QPointF sceneTranslate = translateLine.p2();
1285
1286 //move selection handles
1287 QTransform itemTransform;
1288 itemTransform.translate( sceneTranslate.x(), sceneTranslate.y() );
1289 setTransform( itemTransform );
1290
1291 //handle non-normalised resizes - e.g., dragging the left handle so far to the right that it's past the right handle
1292 if ( mBeginHandleWidth + rx >= 0 && mBeginHandleHeight + ry >= 0 )
1293 {
1294 mResizeRect = QRectF( 0, 0, mBeginHandleWidth + rx, mBeginHandleHeight + ry );
1295 }
1296 else if ( mBeginHandleHeight + ry >= 0 )
1297 {
1298 mResizeRect = QRectF( QPointF( -( mBeginHandleWidth + rx ), 0 ), QPointF( 0, mBeginHandleHeight + ry ) );
1299 }
1300 else if ( mBeginHandleWidth + rx >= 0 )
1301 {
1302 mResizeRect = QRectF( QPointF( 0, -( mBeginHandleHeight + ry ) ), QPointF( mBeginHandleWidth + rx, 0 ) );
1303 }
1304 else
1305 {
1306 mResizeRect = QRectF( QPointF( -( mBeginHandleWidth + rx ), -( mBeginHandleHeight + ry ) ), QPointF( 0, 0 ) );
1307 }
1308
1309 const QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
1310 QRectF newHandleBounds;
1311 for ( QGraphicsItem *item : selectedItems )
1312 {
1313 //get stored item bounds in mouse handle item's coordinate system
1314 QRectF thisItemRect = mapRectFromScene( storedItemRect( item ) );
1315 //now, resize it relative to the current resized dimensions of the mouse handles
1316 relativeResizeRect( thisItemRect, QRectF( -mResizeMoveX, -mResizeMoveY, mBeginHandleWidth, mBeginHandleHeight ), mResizeRect );
1317
1318 thisItemRect = mapRectFromScene( previewSetItemRect( item, mapRectToScene( thisItemRect ) ) );
1319 newHandleBounds = newHandleBounds.isValid() ? newHandleBounds.united( thisItemRect ) : thisItemRect;
1320 }
1321
1322 setRect( newHandleBounds.isValid() ? newHandleBounds : QRectF( 0, 0, std::fabs( mBeginHandleWidth + rx ), std::fabs( mBeginHandleHeight + ry ) ) );
1323
1324 //show current size of selection in status bar
1325 showStatusMessage( tr( "width: %1 mm height: %2 mm" ).arg( rect().width() ).arg( rect().height() ) );
1326}
1327
1328void QgsGraphicsViewMouseHandles::setHandleSize( double size )
1329{
1330 mHandleSize = size;
1331}
1332
1333void QgsGraphicsViewMouseHandles::mouseDoubleClickEvent( QGraphicsSceneMouseEvent *event )
1334{
1335 Q_UNUSED( event )
1336
1337 mDoubleClickInProgress = true;
1338}
1339
1340QSizeF QgsGraphicsViewMouseHandles::calcCursorEdgeOffset( QPointF cursorPos )
1341{
1342 //find offset between cursor position and actual edge of item
1343 QPointF sceneMousePos = mapFromScene( cursorPos );
1344
1345 switch ( mCurrentMouseMoveAction )
1346 {
1347 //vertical resize
1349 return QSizeF( 0, sceneMousePos.y() );
1350
1352 return QSizeF( 0, sceneMousePos.y() - rect().height() );
1353
1354 //horizontal resize
1356 return QSizeF( sceneMousePos.x(), 0 );
1357
1359 return QSizeF( sceneMousePos.x() - rect().width(), 0 );
1360
1361 //diagonal resize
1363 return QSizeF( sceneMousePos.x(), sceneMousePos.y() );
1364
1366 return QSizeF( sceneMousePos.x() - rect().width(), sceneMousePos.y() - rect().height() );
1367
1369 return QSizeF( sceneMousePos.x() - rect().width(), sceneMousePos.y() );
1370
1372 return QSizeF( sceneMousePos.x(), sceneMousePos.y() - rect().height() );
1373
1381 return QSizeF();
1382 }
1383
1384 return QSizeF();
1385}
1386
1387QRectF QgsGraphicsViewMouseHandles::selectionBounds() const
1388{
1389 //calculate bounds of all currently selected items in mouse handle coordinate system
1390 const QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
1391 auto itemIter = selectedItems.constBegin();
1392
1393 //start with handle bounds of first selected item
1394 QRectF bounds = mapFromItem( ( *itemIter ), itemRect( *itemIter ) ).boundingRect();
1395
1396 //iterate through remaining items, expanding the bounds as required
1397 for ( ++itemIter; itemIter != selectedItems.constEnd(); ++itemIter )
1398 {
1399 bounds = bounds.united( mapFromItem( ( *itemIter ), itemRect( *itemIter ) ).boundingRect() );
1400 }
1401
1402 return bounds;
1403}
1404
1405void QgsGraphicsViewMouseHandles::relativeResizeRect( QRectF &rectToResize, const QRectF &boundsBefore, const QRectF &boundsAfter )
1406{
1407 //linearly scale rectToResize relative to the scaling from boundsBefore to boundsAfter
1408 double left = relativePosition( rectToResize.left(), boundsBefore.left(), boundsBefore.right(), boundsAfter.left(), boundsAfter.right() );
1409 double right = relativePosition( rectToResize.right(), boundsBefore.left(), boundsBefore.right(), boundsAfter.left(), boundsAfter.right() );
1410 double top = relativePosition( rectToResize.top(), boundsBefore.top(), boundsBefore.bottom(), boundsAfter.top(), boundsAfter.bottom() );
1411 double bottom = relativePosition( rectToResize.bottom(), boundsBefore.top(), boundsBefore.bottom(), boundsAfter.top(), boundsAfter.bottom() );
1412
1413 rectToResize.setRect( left, top, right - left, bottom - top );
1414}
1415
1416double QgsGraphicsViewMouseHandles::relativePosition( double position, double beforeMin, double beforeMax, double afterMin, double afterMax )
1417{
1418 //calculate parameters for linear scale between before and after ranges
1419 double m = ( afterMax - afterMin ) / ( beforeMax - beforeMin );
1420 double c = afterMin - ( beforeMin * m );
1421
1422 //return linearly scaled position
1423 return m * position + c;
1424}
1425
MouseHandlesAction
Action to be performed by the mouse handles.
Definition qgis.h:6290
@ ResizeRightDown
Resize right down (Bottom right handle).
Definition qgis.h:6299
@ NoAction
No action.
Definition qgis.h:6305
@ SelectItem
Select item.
Definition qgis.h:6304
@ ResizeLeftUp
Resize left up (Top left handle).
Definition qgis.h:6296
@ ResizeLeftDown
Resize left down (Bottom left handle).
Definition qgis.h:6298
@ ResizeRight
Resize right (Right handle).
Definition qgis.h:6295
@ MoveItem
Move item.
Definition qgis.h:6291
@ ResizeDown
Resize down (Bottom handle).
Definition qgis.h:6293
@ RotateTopRight
Rotate from top right handle.
Definition qgis.h:6301
@ RotateTopLeft
Rotate from top left handle.
Definition qgis.h:6300
@ RotateBottomRight
Rotate right bottom right handle.
Definition qgis.h:6303
@ ResizeRightUp
Resize right up (Top right handle).
Definition qgis.h:6297
@ ResizeLeft
Resize left (Left handle).
Definition qgis.h:6294
@ RotateBottomLeft
Rotate from bottom left handle.
Definition qgis.h:6302
@ ResizeUp
Resize up (Top handle).
Definition qgis.h:6292
static double snappedAngle(double angle)
Snaps an angle (in degrees) to its closest 45 degree angle.
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:6900
#define QgsDebugError(str)
Definition qgslogger.h:59