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