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