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