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