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