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