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