QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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 "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 switch ( mouseAction )
232 {
233 case NoAction:
234 return Qt::ForbiddenCursor;
235 case MoveItem:
236 return Qt::SizeAllCursor;
237 case ResizeUp:
238 case ResizeDown:
239 //account for rotation
240 if ( ( rotation() <= 22.5 || rotation() >= 337.5 ) || ( rotation() >= 157.5 && rotation() <= 202.5 ) )
241 {
242 return Qt::SizeVerCursor;
243 }
244 else if ( ( rotation() >= 22.5 && rotation() <= 67.5 ) || ( rotation() >= 202.5 && rotation() <= 247.5 ) )
245 {
246 return Qt::SizeBDiagCursor;
247 }
248 else if ( ( rotation() >= 67.5 && rotation() <= 112.5 ) || ( rotation() >= 247.5 && rotation() <= 292.5 ) )
249 {
250 return Qt::SizeHorCursor;
251 }
252 else
253 {
254 return Qt::SizeFDiagCursor;
255 }
256 case ResizeLeft:
257 case ResizeRight:
258 //account for rotation
259 if ( ( rotation() <= 22.5 || rotation() >= 337.5 ) || ( rotation() >= 157.5 && rotation() <= 202.5 ) )
260 {
261 return Qt::SizeHorCursor;
262 }
263 else if ( ( rotation() >= 22.5 && rotation() <= 67.5 ) || ( rotation() >= 202.5 && rotation() <= 247.5 ) )
264 {
265 return Qt::SizeFDiagCursor;
266 }
267 else if ( ( rotation() >= 67.5 && rotation() <= 112.5 ) || ( rotation() >= 247.5 && rotation() <= 292.5 ) )
268 {
269 return Qt::SizeVerCursor;
270 }
271 else
272 {
273 return Qt::SizeBDiagCursor;
274 }
275
276 case ResizeLeftUp:
277 case ResizeRightDown:
278 //account for rotation
279 if ( ( rotation() <= 22.5 || rotation() >= 337.5 ) || ( rotation() >= 157.5 && rotation() <= 202.5 ) )
280 {
281 return Qt::SizeFDiagCursor;
282 }
283 else if ( ( rotation() >= 22.5 && rotation() <= 67.5 ) || ( rotation() >= 202.5 && rotation() <= 247.5 ) )
284 {
285 return Qt::SizeVerCursor;
286 }
287 else if ( ( rotation() >= 67.5 && rotation() <= 112.5 ) || ( rotation() >= 247.5 && rotation() <= 292.5 ) )
288 {
289 return Qt::SizeBDiagCursor;
290 }
291 else
292 {
293 return Qt::SizeHorCursor;
294 }
295 case ResizeRightUp:
296 case ResizeLeftDown:
297 //account for rotation
298 if ( ( rotation() <= 22.5 || rotation() >= 337.5 ) || ( rotation() >= 157.5 && rotation() <= 202.5 ) )
299 {
300 return Qt::SizeBDiagCursor;
301 }
302 else if ( ( rotation() >= 22.5 && rotation() <= 67.5 ) || ( rotation() >= 202.5 && rotation() <= 247.5 ) )
303 {
304 return Qt::SizeHorCursor;
305 }
306 else if ( ( rotation() >= 67.5 && rotation() <= 112.5 ) || ( rotation() >= 247.5 && rotation() <= 292.5 ) )
307 {
308 return Qt::SizeFDiagCursor;
309 }
310 else
311 {
312 return Qt::SizeVerCursor;
313 }
314 case SelectItem:
315 return Qt::ArrowCursor;
316 }
317
318 return Qt::ArrowCursor;
319}
320
321QgsGraphicsViewMouseHandles::MouseAction QgsGraphicsViewMouseHandles::mouseActionForPosition( QPointF itemCoordPos )
322{
323 bool nearLeftBorder = false;
324 bool nearRightBorder = false;
325 bool nearLowerBorder = false;
326 bool nearUpperBorder = false;
327
328 bool withinWidth = false;
329 bool withinHeight = false;
330 if ( itemCoordPos.x() >= 0 && itemCoordPos.x() <= rect().width() )
331 {
332 withinWidth = true;
333 }
334 if ( itemCoordPos.y() >= 0 && itemCoordPos.y() <= rect().height() )
335 {
336 withinHeight = true;
337 }
338
339 double borderTolerance = rectHandlerBorderTolerance();
340
341 if ( itemCoordPos.x() >= 0 && itemCoordPos.x() < borderTolerance )
342 {
343 nearLeftBorder = true;
344 }
345 if ( itemCoordPos.y() >= 0 && itemCoordPos.y() < borderTolerance )
346 {
347 nearUpperBorder = true;
348 }
349 if ( itemCoordPos.x() <= rect().width() && itemCoordPos.x() > ( rect().width() - borderTolerance ) )
350 {
351 nearRightBorder = true;
352 }
353 if ( itemCoordPos.y() <= rect().height() && itemCoordPos.y() > ( rect().height() - borderTolerance ) )
354 {
355 nearLowerBorder = true;
356 }
357
358 if ( nearLeftBorder && nearUpperBorder )
359 {
360 return QgsGraphicsViewMouseHandles::ResizeLeftUp;
361 }
362 else if ( nearLeftBorder && nearLowerBorder )
363 {
364 return QgsGraphicsViewMouseHandles::ResizeLeftDown;
365 }
366 else if ( nearRightBorder && nearUpperBorder )
367 {
368 return QgsGraphicsViewMouseHandles::ResizeRightUp;
369 }
370 else if ( nearRightBorder && nearLowerBorder )
371 {
372 return QgsGraphicsViewMouseHandles::ResizeRightDown;
373 }
374 else if ( nearLeftBorder && withinHeight )
375 {
376 return QgsGraphicsViewMouseHandles::ResizeLeft;
377 }
378 else if ( nearRightBorder && withinHeight )
379 {
380 return QgsGraphicsViewMouseHandles::ResizeRight;
381 }
382 else if ( nearUpperBorder && withinWidth )
383 {
384 return QgsGraphicsViewMouseHandles::ResizeUp;
385 }
386 else if ( nearLowerBorder && withinWidth )
387 {
388 return QgsGraphicsViewMouseHandles::ResizeDown;
389 }
390
391 //find out if cursor position is over a selected item
392 QPointF scenePoint = mapToScene( itemCoordPos );
393 const QList<QGraphicsItem *> itemsAtCursorPos = sceneItemsAtPoint( scenePoint );
394 if ( itemsAtCursorPos.isEmpty() )
395 {
396 //no items at cursor position
397 return QgsGraphicsViewMouseHandles::SelectItem;
398 }
399 for ( QGraphicsItem *graphicsItem : itemsAtCursorPos )
400 {
401 if ( graphicsItem && graphicsItem->isSelected() )
402 {
403 //cursor is over a selected layout item
404 return QgsGraphicsViewMouseHandles::MoveItem;
405 }
406 }
407
408 //default
409 return QgsGraphicsViewMouseHandles::SelectItem;
410}
411
412QgsGraphicsViewMouseHandles::MouseAction QgsGraphicsViewMouseHandles::mouseActionForScenePos( QPointF sceneCoordPos )
413{
414 // convert sceneCoordPos to item coordinates
415 QPointF itemPos = mapFromScene( sceneCoordPos );
416 return mouseActionForPosition( itemPos );
417}
418
419bool QgsGraphicsViewMouseHandles::shouldBlockEvent( QInputEvent * ) const
420{
421 return mIsDragging || mIsResizing;
422}
423
424void QgsGraphicsViewMouseHandles::selectedItemSizeChanged()
425{
426 if ( !isDragging() && !isResizing() )
427 {
428 //only required for non-mouse initiated size changes
429 updateHandles();
430 }
431}
432
433void QgsGraphicsViewMouseHandles::selectedItemRotationChanged()
434{
435 if ( !isDragging() && !isResizing() )
436 {
437 //only required for non-mouse initiated rotation changes
438 updateHandles();
439 }
440}
441
442void QgsGraphicsViewMouseHandles::hoverMoveEvent( QGraphicsSceneHoverEvent *event )
443{
444 setViewportCursor( cursorForPosition( event->pos() ) );
445}
446
447void QgsGraphicsViewMouseHandles::hoverLeaveEvent( QGraphicsSceneHoverEvent *event )
448{
449 Q_UNUSED( event )
450 setViewportCursor( Qt::ArrowCursor );
451}
452
453void QgsGraphicsViewMouseHandles::mousePressEvent( QGraphicsSceneMouseEvent *event )
454{
455 if ( event->button() != Qt::LeftButton )
456 {
457 event->ignore();
458 return;
459 }
460
461 //save current cursor position
462 mMouseMoveStartPos = event->lastScenePos();
463 mLastMouseEventPos = event->lastScenePos();
464 //save current item geometry
465 mBeginMouseEventPos = event->lastScenePos();
466 mBeginHandlePos = scenePos();
467 mBeginHandleWidth = rect().width();
468 mBeginHandleHeight = rect().height();
469 //type of mouse move action
470 mCurrentMouseMoveAction = mouseActionForPosition( event->pos() );
471
472 hideAlignItems();
473
474 if ( mCurrentMouseMoveAction == MoveItem )
475 {
476 //moving items
477 mIsDragging = true;
478 }
479 else if ( mCurrentMouseMoveAction != SelectItem &&
480 mCurrentMouseMoveAction != NoAction )
481 {
482 //resizing items
483 mIsResizing = true;
484 mResizeRect = QRectF( 0, 0, mBeginHandleWidth, mBeginHandleHeight );
485 mResizeMoveX = 0;
486 mResizeMoveY = 0;
487 mCursorOffset = calcCursorEdgeOffset( mMouseMoveStartPos );
488
489 }
490}
491
492void QgsGraphicsViewMouseHandles::resetStatusBar()
493{
494 const QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
495 int selectedCount = selectedItems.size();
496 if ( selectedCount )
497 {
498 //set status bar message to count of selected items
499 showStatusMessage( tr( "%n item(s) selected", nullptr, selectedCount ) );
500 }
501 else
502 {
503 //clear status bar message
504 showStatusMessage( QString() );
505 }
506}
507
508void QgsGraphicsViewMouseHandles::mouseMoveEvent( QGraphicsSceneMouseEvent *event )
509{
510 if ( isDragging() )
511 {
512 //currently dragging a selection
513 //if shift depressed, constrain movement to horizontal/vertical
514 //if control depressed, ignore snapping
515 dragMouseMove( event->lastScenePos(), event->modifiers() & Qt::ShiftModifier, event->modifiers() & Qt::ControlModifier );
516 }
517 else if ( isResizing() )
518 {
519 //currently resizing a selection
520 //lock aspect ratio if shift depressed
521 //resize from center if alt depressed
522 resizeMouseMove( event->lastScenePos(), event->modifiers() & Qt::ShiftModifier, event->modifiers() & Qt::AltModifier );
523 }
524
525 mLastMouseEventPos = event->lastScenePos();
526}
527
528void QgsGraphicsViewMouseHandles::mouseReleaseEvent( QGraphicsSceneMouseEvent *event )
529{
530 if ( event->button() != Qt::LeftButton )
531 {
532 event->ignore();
533 return;
534 }
535
536 QPointF mouseMoveStopPoint = event->lastScenePos();
537 double diffX = mouseMoveStopPoint.x() - mMouseMoveStartPos.x();
538 double diffY = mouseMoveStopPoint.y() - mMouseMoveStartPos.y();
539
540 //it was only a click
541 if ( std::fabs( diffX ) < std::numeric_limits<double>::min() && std::fabs( diffY ) < std::numeric_limits<double>::min() )
542 {
543 mIsDragging = false;
544 mIsResizing = false;
545 update();
546 hideAlignItems();
547 return;
548 }
549
550 if ( mCurrentMouseMoveAction == MoveItem )
551 {
552 //move selected items
553 startMacroCommand( tr( "Move Items" ) );
554
555 QPointF mEndHandleMovePos = scenePos();
556
557 double deltaX = mEndHandleMovePos.x() - mBeginHandlePos.x();
558 double deltaY = mEndHandleMovePos.y() - mBeginHandlePos.y();
559
560 //move all selected items
561 const QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
562 for ( QGraphicsItem *item : selectedItems )
563 {
564 if ( itemIsLocked( item ) || ( item->flags() & QGraphicsItem::ItemIsSelectable ) == 0 || itemIsGroupMember( item ) )
565 {
566 //don't move locked items, or grouped items (group takes care of that)
567 continue;
568 }
569
570 createItemCommand( item );
571 moveItem( item, deltaX, deltaY );
572 endItemCommand( item );
573 }
574 endMacroCommand();
575 }
576 else if ( mCurrentMouseMoveAction != NoAction )
577 {
578 //resize selected items
579 startMacroCommand( tr( "Resize Items" ) );
580
581 //resize all selected items
582 const QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
583 for ( QGraphicsItem *item : selectedItems )
584 {
585 if ( itemIsLocked( item ) || ( item->flags() & QGraphicsItem::ItemIsSelectable ) == 0 )
586 {
587 //don't resize locked items or deselectable items (e.g., items which make up an item group)
588 continue;
589 }
590 createItemCommand( item );
591
592 QRectF thisItemRect;
593 if ( selectedItems.size() == 1 )
594 {
595 //only a single item is selected, so set its size to the final resized mouse handle size
596 thisItemRect = mResizeRect;
597 }
598 else
599 {
600 //multiple items selected, so each needs to be scaled relatively to the final size of the mouse handles
601 thisItemRect = mapRectFromItem( item, itemRect( item ) );
602 relativeResizeRect( thisItemRect, QRectF( -mResizeMoveX, -mResizeMoveY, mBeginHandleWidth, mBeginHandleHeight ), mResizeRect );
603 }
604
605 thisItemRect = thisItemRect.normalized();
606 QPointF newPos = mapToScene( thisItemRect.topLeft() );
607 thisItemRect.moveTopLeft( newPos );
608 setItemRect( item, thisItemRect );
609
610 endItemCommand( item );
611 }
612 endMacroCommand();
613 }
614
615 hideAlignItems();
616 if ( mIsDragging )
617 {
618 mIsDragging = false;
619 }
620 if ( mIsResizing )
621 {
622 mIsResizing = false;
623 }
624
625 //reset default action
626 mCurrentMouseMoveAction = MoveItem;
627 setViewportCursor( Qt::ArrowCursor );
628 //redraw handles
629 resetTransform();
630 updateHandles();
631 //reset status bar message
632 resetStatusBar();
633}
634
635bool QgsGraphicsViewMouseHandles::selectionRotation( double &rotation ) const
636{
637 //check if all selected items have same rotation
638 QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
639 auto itemIter = selectedItems.constBegin();
640
641 //start with rotation of first selected item
642 double firstItemRotation = ( *itemIter )->rotation();
643
644 //iterate through remaining items, checking if they have same rotation
645 for ( ++itemIter; itemIter != selectedItems.constEnd(); ++itemIter )
646 {
647 if ( !qgsDoubleNear( ( *itemIter )->rotation(), firstItemRotation ) )
648 {
649 //item has a different rotation, so return false
650 return false;
651 }
652 }
653
654 //all items have the same rotation, so set the rotation variable and return true
655 rotation = firstItemRotation;
656 return true;
657}
658
659void QgsGraphicsViewMouseHandles::updateHandles()
660{
661 //recalculate size and position of handle item
662
663 //first check to see if any items are selected
664 QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
665 if ( !selectedItems.isEmpty() )
666 {
667 //one or more items are selected, get bounds of all selected items
668
669 //update rotation of handle object
670 double rotation;
671 if ( selectionRotation( rotation ) )
672 {
673 //all items share a common rotation value, so we rotate the mouse handles to match
674 setRotation( rotation );
675 }
676 else
677 {
678 //items have varying rotation values - we can't rotate the mouse handles to match
679 setRotation( 0 );
680 }
681
682 //get bounds of all selected items
683 QRectF newHandleBounds = selectionBounds();
684
685 //update size and position of handle object
686 setRect( 0, 0, newHandleBounds.width(), newHandleBounds.height() );
687 setPos( mapToScene( newHandleBounds.topLeft() ) );
688
689 show();
690 }
691 else
692 {
693 //no items selected, hide handles
694 hide();
695 }
696 //force redraw
697 update();
698}
699
700void QgsGraphicsViewMouseHandles::dragMouseMove( QPointF currentPosition, bool lockMovement, bool preventSnap )
701{
702 if ( !scene() )
703 {
704 return;
705 }
706
707 //calculate total amount of mouse movement since drag began
708 double moveX = currentPosition.x() - mBeginMouseEventPos.x();
709 double moveY = currentPosition.y() - mBeginMouseEventPos.y();
710
711 //find target position before snapping (in scene coordinates)
712 QPointF upperLeftPoint( mBeginHandlePos.x() + moveX, mBeginHandlePos.y() + moveY );
713
714 QPointF snappedLeftPoint;
715
716 //no snapping for rotated items for now
717 if ( !preventSnap && qgsDoubleNear( rotation(), 0.0 ) )
718 {
719 //snap to grid and guides
720 snappedLeftPoint = snapPoint( upperLeftPoint, Item );
721 }
722 else
723 {
724 //no snapping
725 snappedLeftPoint = upperLeftPoint;
726 hideAlignItems();
727 }
728
729 //calculate total shift for item from beginning of drag operation to current position
730 double moveRectX = snappedLeftPoint.x() - mBeginHandlePos.x();
731 double moveRectY = snappedLeftPoint.y() - mBeginHandlePos.y();
732
733 if ( lockMovement )
734 {
735 //constrained (shift) moving should lock to horizontal/vertical movement
736 //reset the smaller of the x/y movements
737 if ( std::fabs( moveRectX ) <= std::fabs( moveRectY ) )
738 {
739 moveRectX = 0;
740 }
741 else
742 {
743 moveRectY = 0;
744 }
745 }
746
747 //shift handle item to new position
748 QTransform moveTransform;
749 moveTransform.translate( moveRectX, moveRectY );
750 setTransform( moveTransform );
751
752 const QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
753 for ( QGraphicsItem *item : selectedItems )
754 {
755 previewItemMove( item, moveRectX, moveRectY );
756 }
757 //show current displacement of selection in status bar
758 showStatusMessage( tr( "dx: %1 mm dy: %2 mm" ).arg( moveRectX ).arg( moveRectY ) );
759}
760
761void QgsGraphicsViewMouseHandles::resizeMouseMove( QPointF currentPosition, bool lockRatio, bool fromCenter )
762{
763 if ( !scene() )
764 {
765 return;
766 }
767
768 double mx = 0.0, my = 0.0, rx = 0.0, ry = 0.0;
769
770 QPointF beginMousePos;
771 QPointF finalPosition;
772 if ( qgsDoubleNear( rotation(), 0.0 ) )
773 {
774 //snapping only occurs if handles are not rotated for now
775
776 bool snapVertical = mCurrentMouseMoveAction == ResizeLeft ||
777 mCurrentMouseMoveAction == ResizeRight ||
778 mCurrentMouseMoveAction == ResizeLeftUp ||
779 mCurrentMouseMoveAction == ResizeRightUp ||
780 mCurrentMouseMoveAction == ResizeLeftDown ||
781 mCurrentMouseMoveAction == ResizeRightDown;
782
783 bool snapHorizontal = mCurrentMouseMoveAction == ResizeUp ||
784 mCurrentMouseMoveAction == ResizeDown ||
785 mCurrentMouseMoveAction == ResizeLeftUp ||
786 mCurrentMouseMoveAction == ResizeRightUp ||
787 mCurrentMouseMoveAction == ResizeLeftDown ||
788 mCurrentMouseMoveAction == ResizeRightDown;
789
790 //subtract cursor edge offset from begin mouse event and current cursor position, so that snapping occurs to edge of mouse handles
791 //rather then cursor position
792 beginMousePos = mapFromScene( QPointF( mBeginMouseEventPos.x() - mCursorOffset.width(), mBeginMouseEventPos.y() - mCursorOffset.height() ) );
793 QPointF snappedPosition = snapPoint( QPointF( currentPosition.x() - mCursorOffset.width(), currentPosition.y() - mCursorOffset.height() ), Point, snapHorizontal, snapVertical );
794 finalPosition = mapFromScene( snappedPosition );
795 }
796 else
797 {
798 //no snapping for rotated items for now
799 beginMousePos = mapFromScene( mBeginMouseEventPos );
800 finalPosition = mapFromScene( currentPosition );
801 }
802
803 double diffX = finalPosition.x() - beginMousePos.x();
804 double diffY = finalPosition.y() - beginMousePos.y();
805
806 double ratio = 0;
807 if ( lockRatio && !qgsDoubleNear( mBeginHandleHeight, 0.0 ) )
808 {
809 ratio = mBeginHandleWidth / mBeginHandleHeight;
810 }
811
812 switch ( mCurrentMouseMoveAction )
813 {
814 //vertical resize
815 case ResizeUp:
816 {
817 if ( ratio )
818 {
819 diffX = ( ( mBeginHandleHeight - diffY ) * ratio ) - mBeginHandleWidth;
820 mx = -diffX / 2;
821 my = diffY;
822 rx = diffX;
823 ry = -diffY;
824 }
825 else
826 {
827 mx = 0;
828 my = diffY;
829 rx = 0;
830 ry = -diffY;
831 }
832 break;
833 }
834
835 case ResizeDown:
836 {
837 if ( ratio )
838 {
839 diffX = ( ( mBeginHandleHeight + diffY ) * ratio ) - mBeginHandleWidth;
840 mx = -diffX / 2;
841 my = 0;
842 rx = diffX;
843 ry = diffY;
844 }
845 else
846 {
847 mx = 0;
848 my = 0;
849 rx = 0;
850 ry = diffY;
851 }
852 break;
853 }
854
855 //horizontal resize
856 case ResizeLeft:
857 {
858 if ( ratio )
859 {
860 diffY = ( ( mBeginHandleWidth - diffX ) / ratio ) - mBeginHandleHeight;
861 mx = diffX;
862 my = -diffY / 2;
863 rx = -diffX;
864 ry = diffY;
865 }
866 else
867 {
868 mx = diffX, my = 0;
869 rx = -diffX;
870 ry = 0;
871 }
872 break;
873 }
874
875 case ResizeRight:
876 {
877 if ( ratio )
878 {
879 diffY = ( ( mBeginHandleWidth + diffX ) / ratio ) - mBeginHandleHeight;
880 mx = 0;
881 my = -diffY / 2;
882 rx = diffX;
883 ry = diffY;
884 }
885 else
886 {
887 mx = 0;
888 my = 0;
889 rx = diffX, ry = 0;
890 }
891 break;
892 }
893
894 //diagonal resize
895 case ResizeLeftUp:
896 {
897 if ( ratio )
898 {
899 //ratio locked resize
900 if ( ( mBeginHandleWidth - diffX ) / ( mBeginHandleHeight - diffY ) > ratio )
901 {
902 diffX = mBeginHandleWidth - ( ( mBeginHandleHeight - diffY ) * ratio );
903 }
904 else
905 {
906 diffY = mBeginHandleHeight - ( ( mBeginHandleWidth - diffX ) / ratio );
907 }
908 }
909 mx = diffX, my = diffY;
910 rx = -diffX;
911 ry = -diffY;
912 break;
913 }
914
915 case ResizeRightDown:
916 {
917 if ( ratio )
918 {
919 //ratio locked resize
920 if ( ( mBeginHandleWidth + diffX ) / ( mBeginHandleHeight + diffY ) > ratio )
921 {
922 diffX = ( ( mBeginHandleHeight + diffY ) * ratio ) - mBeginHandleWidth;
923 }
924 else
925 {
926 diffY = ( ( mBeginHandleWidth + diffX ) / ratio ) - mBeginHandleHeight;
927 }
928 }
929 mx = 0;
930 my = 0;
931 rx = diffX, ry = diffY;
932 break;
933 }
934
935 case ResizeRightUp:
936 {
937 if ( ratio )
938 {
939 //ratio locked resize
940 if ( ( mBeginHandleWidth + diffX ) / ( mBeginHandleHeight - diffY ) > ratio )
941 {
942 diffX = ( ( mBeginHandleHeight - diffY ) * ratio ) - mBeginHandleWidth;
943 }
944 else
945 {
946 diffY = mBeginHandleHeight - ( ( mBeginHandleWidth + diffX ) / ratio );
947 }
948 }
949 mx = 0;
950 my = diffY, rx = diffX, ry = -diffY;
951 break;
952 }
953
954 case ResizeLeftDown:
955 {
956 if ( ratio )
957 {
958 //ratio locked resize
959 if ( ( mBeginHandleWidth - diffX ) / ( mBeginHandleHeight + diffY ) > ratio )
960 {
961 diffX = mBeginHandleWidth - ( ( mBeginHandleHeight + diffY ) * ratio );
962 }
963 else
964 {
965 diffY = ( ( mBeginHandleWidth - diffX ) / ratio ) - mBeginHandleHeight;
966 }
967 }
968 mx = diffX, my = 0;
969 rx = -diffX;
970 ry = diffY;
971 break;
972 }
973
974 case MoveItem:
975 case SelectItem:
976 case NoAction:
977 break;
978 }
979
980 //resizing from center of objects?
981 if ( fromCenter )
982 {
983 my = -ry;
984 mx = -rx;
985 ry = 2 * ry;
986 rx = 2 * rx;
987 }
988
989 //update selection handle rectangle
990
991 //make sure selection handle size rectangle is normalized (ie, left coord < right coord)
992 mResizeMoveX = mBeginHandleWidth + rx > 0 ? mx : mx + mBeginHandleWidth + rx;
993 mResizeMoveY = mBeginHandleHeight + ry > 0 ? my : my + mBeginHandleHeight + ry;
994
995 //calculate movement in scene coordinates
996 QLineF translateLine = QLineF( 0, 0, mResizeMoveX, mResizeMoveY );
997 translateLine.setAngle( translateLine.angle() - rotation() );
998 QPointF sceneTranslate = translateLine.p2();
999
1000 //move selection handles
1001 QTransform itemTransform;
1002 itemTransform.translate( sceneTranslate.x(), sceneTranslate.y() );
1003 setTransform( itemTransform );
1004
1005 //handle non-normalised resizes - e.g., dragging the left handle so far to the right that it's past the right handle
1006 if ( mBeginHandleWidth + rx >= 0 && mBeginHandleHeight + ry >= 0 )
1007 {
1008 mResizeRect = QRectF( 0, 0, mBeginHandleWidth + rx, mBeginHandleHeight + ry );
1009 }
1010 else if ( mBeginHandleHeight + ry >= 0 )
1011 {
1012 mResizeRect = QRectF( QPointF( -( mBeginHandleWidth + rx ), 0 ), QPointF( 0, mBeginHandleHeight + ry ) );
1013 }
1014 else if ( mBeginHandleWidth + rx >= 0 )
1015 {
1016 mResizeRect = QRectF( QPointF( 0, -( mBeginHandleHeight + ry ) ), QPointF( mBeginHandleWidth + rx, 0 ) );
1017 }
1018 else
1019 {
1020 mResizeRect = QRectF( QPointF( -( mBeginHandleWidth + rx ), -( mBeginHandleHeight + ry ) ), QPointF( 0, 0 ) );
1021 }
1022
1023 const QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
1024 QRectF newHandleBounds;
1025 for ( QGraphicsItem *item : selectedItems )
1026 {
1027 //get stored item bounds in mouse handle item's coordinate system
1028 QRectF thisItemRect = mapRectFromScene( storedItemRect( item ) );
1029 //now, resize it relative to the current resized dimensions of the mouse handles
1030 relativeResizeRect( thisItemRect, QRectF( -mResizeMoveX, -mResizeMoveY, mBeginHandleWidth, mBeginHandleHeight ), mResizeRect );
1031
1032 thisItemRect = mapRectFromScene( previewSetItemRect( item, mapRectToScene( thisItemRect ) ) );
1033 newHandleBounds = newHandleBounds.isValid() ? newHandleBounds.united( thisItemRect ) : thisItemRect;
1034 }
1035
1036 setRect( newHandleBounds.isValid() ? newHandleBounds : QRectF( 0, 0, std::fabs( mBeginHandleWidth + rx ), std::fabs( mBeginHandleHeight + ry ) ) );
1037
1038 //show current size of selection in status bar
1039 showStatusMessage( tr( "width: %1 mm height: %2 mm" ).arg( rect().width() ).arg( rect().height() ) );
1040
1041}
1042
1043void QgsGraphicsViewMouseHandles::setHandleSize( double size )
1044{
1045 mHandleSize = size;
1046}
1047
1048void QgsGraphicsViewMouseHandles::mouseDoubleClickEvent( QGraphicsSceneMouseEvent *event )
1049{
1050 Q_UNUSED( event )
1051}
1052
1053QSizeF QgsGraphicsViewMouseHandles::calcCursorEdgeOffset( QPointF cursorPos )
1054{
1055 //find offset between cursor position and actual edge of item
1056 QPointF sceneMousePos = mapFromScene( cursorPos );
1057
1058 switch ( mCurrentMouseMoveAction )
1059 {
1060 //vertical resize
1061 case QgsGraphicsViewMouseHandles::ResizeUp:
1062 return QSizeF( 0, sceneMousePos.y() );
1063
1064 case QgsGraphicsViewMouseHandles::ResizeDown:
1065 return QSizeF( 0, sceneMousePos.y() - rect().height() );
1066
1067 //horizontal resize
1068 case QgsGraphicsViewMouseHandles::ResizeLeft:
1069 return QSizeF( sceneMousePos.x(), 0 );
1070
1071 case QgsGraphicsViewMouseHandles::ResizeRight:
1072 return QSizeF( sceneMousePos.x() - rect().width(), 0 );
1073
1074 //diagonal resize
1075 case QgsGraphicsViewMouseHandles::ResizeLeftUp:
1076 return QSizeF( sceneMousePos.x(), sceneMousePos.y() );
1077
1078 case QgsGraphicsViewMouseHandles::ResizeRightDown:
1079 return QSizeF( sceneMousePos.x() - rect().width(), sceneMousePos.y() - rect().height() );
1080
1081 case QgsGraphicsViewMouseHandles::ResizeRightUp:
1082 return QSizeF( sceneMousePos.x() - rect().width(), sceneMousePos.y() );
1083
1084 case QgsGraphicsViewMouseHandles::ResizeLeftDown:
1085 return QSizeF( sceneMousePos.x(), sceneMousePos.y() - rect().height() );
1086
1087 case MoveItem:
1088 case SelectItem:
1089 case NoAction:
1090 return QSizeF();
1091 }
1092
1093 return QSizeF();
1094}
1095
1096QRectF QgsGraphicsViewMouseHandles::selectionBounds() const
1097{
1098 //calculate bounds of all currently selected items in mouse handle coordinate system
1099 const QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
1100 auto itemIter = selectedItems.constBegin();
1101
1102 //start with handle bounds of first selected item
1103 QRectF bounds = mapFromItem( ( *itemIter ), itemRect( *itemIter ) ).boundingRect();
1104
1105 //iterate through remaining items, expanding the bounds as required
1106 for ( ++itemIter; itemIter != selectedItems.constEnd(); ++itemIter )
1107 {
1108 bounds = bounds.united( mapFromItem( ( *itemIter ), itemRect( *itemIter ) ).boundingRect() );
1109 }
1110
1111 return bounds;
1112}
1113
1114void QgsGraphicsViewMouseHandles::relativeResizeRect( QRectF &rectToResize, const QRectF &boundsBefore, const QRectF &boundsAfter )
1115{
1116 //linearly scale rectToResize relative to the scaling from boundsBefore to boundsAfter
1117 double left = relativePosition( rectToResize.left(), boundsBefore.left(), boundsBefore.right(), boundsAfter.left(), boundsAfter.right() );
1118 double right = relativePosition( rectToResize.right(), boundsBefore.left(), boundsBefore.right(), boundsAfter.left(), boundsAfter.right() );
1119 double top = relativePosition( rectToResize.top(), boundsBefore.top(), boundsBefore.bottom(), boundsAfter.top(), boundsAfter.bottom() );
1120 double bottom = relativePosition( rectToResize.bottom(), boundsBefore.top(), boundsBefore.bottom(), boundsAfter.top(), boundsAfter.bottom() );
1121
1122 rectToResize.setRect( left, top, right - left, bottom - top );
1123}
1124
1125double QgsGraphicsViewMouseHandles::relativePosition( double position, double beforeMin, double beforeMax, double afterMin, double afterMax )
1126{
1127 //calculate parameters for linear scale between before and after ranges
1128 double m = ( afterMax - afterMin ) / ( beforeMax - beforeMin );
1129 double c = afterMin - ( beforeMin * m );
1130
1131 //return linearly scaled position
1132 return m * position + c;
1133}
1134
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:2527