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