mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-15 00:02:52 -04:00
1393 lines
45 KiB
C++
1393 lines
45 KiB
C++
/***************************************************************************
|
|
qgsgraphicsviewmousehandles.cpp
|
|
------------------------
|
|
begin : March 2020
|
|
copyright : (C) 2020 by Nyall Dawson
|
|
email : nyall.dawson@gmail.com
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include "qgsgraphicsviewmousehandles.h"
|
|
#include "moc_qgsgraphicsviewmousehandles.cpp"
|
|
#include "qgsrendercontext.h"
|
|
#include "qgis.h"
|
|
#include <QGraphicsView>
|
|
#include <QGraphicsSceneHoverEvent>
|
|
#include <QPainter>
|
|
#include <QWidget>
|
|
#include <limits>
|
|
|
|
///@cond PRIVATE
|
|
|
|
QgsGraphicsViewMouseHandles::QgsGraphicsViewMouseHandles( QGraphicsView *view )
|
|
: QObject( nullptr )
|
|
, QGraphicsRectItem( nullptr )
|
|
, mView( view )
|
|
{
|
|
//accept hover events, required for changing cursor to resize cursors
|
|
setAcceptHoverEvents( true );
|
|
|
|
//prepare rotation handle path
|
|
mRotationHandlePath.moveTo( 0, 14 );
|
|
mRotationHandlePath.lineTo( 6, 20 );
|
|
mRotationHandlePath.lineTo( 12, 14 );
|
|
mRotationHandlePath.arcTo( 8, 8, 12, 12, 180, -90 );
|
|
mRotationHandlePath.lineTo( 14, 12 );
|
|
mRotationHandlePath.lineTo( 20, 6 );
|
|
mRotationHandlePath.lineTo( 14, 0 );
|
|
mRotationHandlePath.arcTo( 4, 4, 20, 20, 90, 90 );
|
|
mRotationHandlePath.lineTo( 0, 14 );
|
|
}
|
|
|
|
void QgsGraphicsViewMouseHandles::setRotationEnabled( bool enable )
|
|
{
|
|
if ( mRotationEnabled == enable )
|
|
{
|
|
return;
|
|
}
|
|
|
|
mRotationEnabled = enable;
|
|
update();
|
|
}
|
|
|
|
void QgsGraphicsViewMouseHandles::paintInternal( QPainter *painter, bool showHandles, bool showStaticBoundingBoxes, bool showTemporaryBoundingBoxes, const QStyleOptionGraphicsItem *, QWidget * )
|
|
{
|
|
if ( !showHandles )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( showStaticBoundingBoxes )
|
|
{
|
|
//draw resize handles around bounds of entire selection
|
|
double rectHandlerSize = rectHandlerBorderTolerance();
|
|
drawHandles( painter, rectHandlerSize );
|
|
}
|
|
|
|
if ( showTemporaryBoundingBoxes && ( mIsResizing || mIsDragging || showStaticBoundingBoxes ) )
|
|
{
|
|
//draw dotted boxes around selected items
|
|
drawSelectedItemBounds( painter );
|
|
}
|
|
}
|
|
|
|
QRectF QgsGraphicsViewMouseHandles::storedItemRect( QGraphicsItem *item ) const
|
|
{
|
|
return itemRect( item );
|
|
}
|
|
|
|
void QgsGraphicsViewMouseHandles::rotateItem( QGraphicsItem *, double, double, double )
|
|
{
|
|
}
|
|
|
|
void QgsGraphicsViewMouseHandles::previewItemMove( QGraphicsItem *, double, double )
|
|
{
|
|
}
|
|
|
|
QRectF QgsGraphicsViewMouseHandles::previewSetItemRect( QGraphicsItem *, QRectF )
|
|
{
|
|
return QRectF();
|
|
}
|
|
|
|
void QgsGraphicsViewMouseHandles::startMacroCommand( const QString & )
|
|
{
|
|
}
|
|
|
|
void QgsGraphicsViewMouseHandles::endMacroCommand()
|
|
{
|
|
}
|
|
|
|
void QgsGraphicsViewMouseHandles::endItemCommand( QGraphicsItem * )
|
|
{
|
|
}
|
|
|
|
void QgsGraphicsViewMouseHandles::createItemCommand( QGraphicsItem * )
|
|
{
|
|
}
|
|
|
|
QPointF QgsGraphicsViewMouseHandles::snapPoint( QPointF originalPoint, QgsGraphicsViewMouseHandles::SnapGuideMode, bool, bool )
|
|
{
|
|
return originalPoint;
|
|
}
|
|
|
|
void QgsGraphicsViewMouseHandles::expandItemList( const QList<QGraphicsItem *> &items, QList<QGraphicsItem *> &collected ) const
|
|
{
|
|
collected = items;
|
|
}
|
|
|
|
void QgsGraphicsViewMouseHandles::drawHandles( QPainter *painter, double rectHandlerSize )
|
|
{
|
|
//blue, zero width cosmetic pen for outline
|
|
QPen handlePen = QPen( QColor( 55, 140, 195, 255 ) );
|
|
handlePen.setWidth( 0 );
|
|
painter->setPen( handlePen );
|
|
|
|
//draw box around entire selection bounds
|
|
painter->setBrush( Qt::NoBrush );
|
|
painter->drawRect( QRectF( 0, 0, rect().width(), rect().height() ) );
|
|
|
|
//draw resize handles, using filled white boxes
|
|
painter->setBrush( QColor( 255, 255, 255, 255 ) );
|
|
//top left
|
|
painter->drawRect( QRectF( 0, 0, rectHandlerSize, rectHandlerSize ) );
|
|
//mid top
|
|
painter->drawRect( QRectF( ( rect().width() - rectHandlerSize ) / 2, 0, rectHandlerSize, rectHandlerSize ) );
|
|
//top right
|
|
painter->drawRect( QRectF( rect().width() - rectHandlerSize, 0, rectHandlerSize, rectHandlerSize ) );
|
|
//mid left
|
|
painter->drawRect( QRectF( 0, ( rect().height() - rectHandlerSize ) / 2, rectHandlerSize, rectHandlerSize ) );
|
|
//mid right
|
|
painter->drawRect( QRectF( rect().width() - rectHandlerSize, ( rect().height() - rectHandlerSize ) / 2, rectHandlerSize, rectHandlerSize ) );
|
|
//bottom left
|
|
painter->drawRect( QRectF( 0, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) );
|
|
//mid bottom
|
|
painter->drawRect( QRectF( ( rect().width() - rectHandlerSize ) / 2, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) );
|
|
//bottom right
|
|
painter->drawRect( QRectF( rect().width() - rectHandlerSize, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) );
|
|
|
|
if ( isRotationEnabled() )
|
|
{
|
|
//draw rotate handles
|
|
const double scale = rectHandlerSize / mHandleSize;
|
|
const bool drawBottomRotationHandles = ( rectHandlerSize * 2 ) + ( mRotationHandleSize * scale * 2 ) < rect().height();
|
|
const bool drawRightRotationHandles = ( rectHandlerSize * 2 ) + ( mRotationHandleSize * scale * 2 ) < rect().width();
|
|
QTransform transform;
|
|
|
|
//top left
|
|
transform.reset();
|
|
transform.translate( rectHandlerSize, rectHandlerSize );
|
|
transform.scale( scale, scale );
|
|
painter->save();
|
|
painter->setTransform( transform, true );
|
|
painter->drawPath( mRotationHandlePath );
|
|
painter->restore();
|
|
|
|
//top right
|
|
if ( drawRightRotationHandles )
|
|
{
|
|
transform.reset();
|
|
transform.translate( rect().width() - rectHandlerSize, rectHandlerSize );
|
|
transform.rotate( 90 );
|
|
transform.scale( scale, scale );
|
|
painter->save();
|
|
painter->setTransform( transform, true );
|
|
painter->drawPath( mRotationHandlePath );
|
|
painter->restore();
|
|
}
|
|
|
|
if ( drawBottomRotationHandles )
|
|
{
|
|
//bottom left
|
|
transform.reset();
|
|
transform.translate( rectHandlerSize, rect().height() - rectHandlerSize );
|
|
transform.rotate( 270 );
|
|
transform.scale( scale, scale );
|
|
painter->save();
|
|
painter->setTransform( transform, true );
|
|
painter->drawPath( mRotationHandlePath );
|
|
painter->restore();
|
|
}
|
|
|
|
if ( drawBottomRotationHandles && drawRightRotationHandles )
|
|
{
|
|
//bottom right
|
|
transform.reset();
|
|
transform.translate( rect().width() - rectHandlerSize, rect().height() - rectHandlerSize );
|
|
transform.rotate( 180 );
|
|
transform.scale( scale, scale );
|
|
painter->save();
|
|
painter->setTransform( transform, true );
|
|
painter->drawPath( mRotationHandlePath );
|
|
painter->restore();
|
|
}
|
|
}
|
|
}
|
|
|
|
void QgsGraphicsViewMouseHandles::drawSelectedItemBounds( QPainter *painter )
|
|
{
|
|
//draw dotted border around selected items to give visual feedback which items are selected
|
|
const QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
|
|
if ( selectedItems.isEmpty() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
QList<QGraphicsItem *> itemsToDraw;
|
|
expandItemList( selectedItems, itemsToDraw );
|
|
|
|
if ( itemsToDraw.size() <= 1 )
|
|
{
|
|
// Single item selected. The items bounds are drawn by the MouseHandles itself.
|
|
return;
|
|
}
|
|
|
|
//use difference mode so that they are visible regardless of item colors
|
|
QgsScopedQPainterState painterState( painter );
|
|
painter->setCompositionMode( QPainter::CompositionMode_Difference );
|
|
|
|
// use a grey dashed pen - in difference mode this should always be visible
|
|
QPen selectedItemPen = QPen( QColor( 144, 144, 144, 255 ) );
|
|
selectedItemPen.setStyle( Qt::DashLine );
|
|
selectedItemPen.setWidth( 0 );
|
|
painter->setPen( selectedItemPen );
|
|
painter->setBrush( Qt::NoBrush );
|
|
|
|
for ( QGraphicsItem *item : std::as_const( itemsToDraw ) )
|
|
{
|
|
//get bounds of selected item
|
|
QPolygonF itemBounds;
|
|
if ( isDragging() && !itemIsLocked( item ) )
|
|
{
|
|
//if currently dragging, draw selected item bounds relative to current mouse position
|
|
//first, get bounds of current item in scene coordinates
|
|
QPolygonF itemSceneBounds = item->mapToScene( itemRect( item ) );
|
|
//now, translate it by the current movement amount
|
|
//IMPORTANT - this is done in scene coordinates, since we don't want any rotation/non-translation transforms to affect the movement
|
|
itemSceneBounds.translate( transform().dx(), transform().dy() );
|
|
//finally, remap it to the mouse handle item's coordinate system so it's ready for drawing
|
|
itemBounds = mapFromScene( itemSceneBounds );
|
|
}
|
|
else if ( isResizing() && !itemIsLocked( item ) )
|
|
{
|
|
//if currently resizing, calculate relative resize of this item
|
|
//get item bounds in mouse handle item's coordinate system
|
|
QRectF thisItemRect = mapRectFromItem( item, itemRect( item ) );
|
|
//now, resize it relative to the current resized dimensions of the mouse handles
|
|
relativeResizeRect( thisItemRect, QRectF( -mResizeMoveX, -mResizeMoveY, mBeginHandleWidth, mBeginHandleHeight ), mResizeRect );
|
|
itemBounds = QPolygonF( thisItemRect );
|
|
}
|
|
else if ( isRotating() && !itemIsLocked( item ) )
|
|
{
|
|
const QPolygonF itemSceneBounds = item->mapToScene( itemRect( item ) );
|
|
const QPointF rotationCenter = sceneTransform().map( rect().center() );
|
|
|
|
QTransform transform;
|
|
transform.translate( rotationCenter.x(), rotationCenter.y() );
|
|
transform.rotate( mRotationDelta );
|
|
transform.translate( -rotationCenter.x(), -rotationCenter.y() );
|
|
itemBounds = mapFromScene( transform.map( itemSceneBounds ) );
|
|
}
|
|
else
|
|
{
|
|
// not resizing or moving, so just map the item's bounds to the mouse handle item's coordinate system
|
|
itemBounds = item->mapToItem( this, itemRect( item ) );
|
|
}
|
|
|
|
// drawPolygon causes issues on windows - corners of path may be missing resulting in triangles being drawn
|
|
// instead of rectangles! (Same cause as #13343)
|
|
QPainterPath path;
|
|
path.addPolygon( itemBounds );
|
|
painter->drawPath( path );
|
|
}
|
|
}
|
|
|
|
double QgsGraphicsViewMouseHandles::rectHandlerBorderTolerance() const
|
|
{
|
|
if ( !mView )
|
|
return 0;
|
|
|
|
//calculate size for resize handles
|
|
//get view scale factor
|
|
double viewScaleFactor = mView->transform().m11();
|
|
|
|
//size of handle boxes depends on zoom level in layout view
|
|
double rectHandlerSize = mHandleSize / viewScaleFactor;
|
|
|
|
//make sure the boxes don't get too large
|
|
if ( rectHandlerSize > ( rect().width() / 3 ) )
|
|
{
|
|
rectHandlerSize = rect().width() / 3;
|
|
}
|
|
if ( rectHandlerSize > ( rect().height() / 3 ) )
|
|
{
|
|
rectHandlerSize = rect().height() / 3;
|
|
}
|
|
return rectHandlerSize;
|
|
}
|
|
|
|
Qt::CursorShape QgsGraphicsViewMouseHandles::cursorForPosition( QPointF itemCoordPos )
|
|
{
|
|
Qgis::MouseHandlesAction mouseAction = mouseActionForPosition( itemCoordPos );
|
|
double normalizedRotation = std::fmod( rotation(), 360 );
|
|
if ( normalizedRotation < 0 )
|
|
{
|
|
normalizedRotation += 360;
|
|
}
|
|
switch ( mouseAction )
|
|
{
|
|
case Qgis::MouseHandlesAction::NoAction:
|
|
return Qt::ForbiddenCursor;
|
|
case Qgis::MouseHandlesAction::MoveItem:
|
|
return Qt::SizeAllCursor;
|
|
case Qgis::MouseHandlesAction::ResizeUp:
|
|
case Qgis::MouseHandlesAction::ResizeDown:
|
|
//account for rotation
|
|
if ( ( normalizedRotation <= 22.5 || normalizedRotation >= 337.5 ) || ( normalizedRotation >= 157.5 && normalizedRotation <= 202.5 ) )
|
|
{
|
|
return Qt::SizeVerCursor;
|
|
}
|
|
else if ( ( normalizedRotation >= 22.5 && normalizedRotation <= 67.5 ) || ( normalizedRotation >= 202.5 && normalizedRotation <= 247.5 ) )
|
|
{
|
|
return Qt::SizeBDiagCursor;
|
|
}
|
|
else if ( ( normalizedRotation >= 67.5 && normalizedRotation <= 112.5 ) || ( normalizedRotation >= 247.5 && normalizedRotation <= 292.5 ) )
|
|
{
|
|
return Qt::SizeHorCursor;
|
|
}
|
|
else
|
|
{
|
|
return Qt::SizeFDiagCursor;
|
|
}
|
|
case Qgis::MouseHandlesAction::ResizeLeft:
|
|
case Qgis::MouseHandlesAction::ResizeRight:
|
|
//account for rotation
|
|
if ( ( normalizedRotation <= 22.5 || normalizedRotation >= 337.5 ) || ( normalizedRotation >= 157.5 && normalizedRotation <= 202.5 ) )
|
|
{
|
|
return Qt::SizeHorCursor;
|
|
}
|
|
else if ( ( normalizedRotation >= 22.5 && normalizedRotation <= 67.5 ) || ( normalizedRotation >= 202.5 && normalizedRotation <= 247.5 ) )
|
|
{
|
|
return Qt::SizeFDiagCursor;
|
|
}
|
|
else if ( ( normalizedRotation >= 67.5 && normalizedRotation <= 112.5 ) || ( normalizedRotation >= 247.5 && normalizedRotation <= 292.5 ) )
|
|
{
|
|
return Qt::SizeVerCursor;
|
|
}
|
|
else
|
|
{
|
|
return Qt::SizeBDiagCursor;
|
|
}
|
|
|
|
case Qgis::MouseHandlesAction::ResizeLeftUp:
|
|
case Qgis::MouseHandlesAction::ResizeRightDown:
|
|
//account for rotation
|
|
if ( ( normalizedRotation <= 22.5 || normalizedRotation >= 337.5 ) || ( normalizedRotation >= 157.5 && normalizedRotation <= 202.5 ) )
|
|
{
|
|
return Qt::SizeFDiagCursor;
|
|
}
|
|
else if ( ( normalizedRotation >= 22.5 && normalizedRotation <= 67.5 ) || ( normalizedRotation >= 202.5 && normalizedRotation <= 247.5 ) )
|
|
{
|
|
return Qt::SizeVerCursor;
|
|
}
|
|
else if ( ( normalizedRotation >= 67.5 && normalizedRotation <= 112.5 ) || ( normalizedRotation >= 247.5 && normalizedRotation <= 292.5 ) )
|
|
{
|
|
return Qt::SizeBDiagCursor;
|
|
}
|
|
else
|
|
{
|
|
return Qt::SizeHorCursor;
|
|
}
|
|
case Qgis::MouseHandlesAction::ResizeRightUp:
|
|
case Qgis::MouseHandlesAction::ResizeLeftDown:
|
|
//account for rotation
|
|
if ( ( normalizedRotation <= 22.5 || normalizedRotation >= 337.5 ) || ( normalizedRotation >= 157.5 && normalizedRotation <= 202.5 ) )
|
|
{
|
|
return Qt::SizeBDiagCursor;
|
|
}
|
|
else if ( ( normalizedRotation >= 22.5 && normalizedRotation <= 67.5 ) || ( normalizedRotation >= 202.5 && normalizedRotation <= 247.5 ) )
|
|
{
|
|
return Qt::SizeHorCursor;
|
|
}
|
|
else if ( ( normalizedRotation >= 67.5 && normalizedRotation <= 112.5 ) || ( normalizedRotation >= 247.5 && normalizedRotation <= 292.5 ) )
|
|
{
|
|
return Qt::SizeFDiagCursor;
|
|
}
|
|
else
|
|
{
|
|
return Qt::SizeVerCursor;
|
|
}
|
|
case Qgis::MouseHandlesAction::SelectItem:
|
|
return Qt::ArrowCursor;
|
|
|
|
case Qgis::MouseHandlesAction::RotateLeftUp:
|
|
case Qgis::MouseHandlesAction::RotateRightUp:
|
|
case Qgis::MouseHandlesAction::RotateLeftDown:
|
|
case Qgis::MouseHandlesAction::RotateRightDown:
|
|
return Qt::PointingHandCursor;
|
|
}
|
|
|
|
return Qt::ArrowCursor;
|
|
}
|
|
|
|
Qgis::MouseHandlesAction QgsGraphicsViewMouseHandles::mouseActionForPosition( QPointF itemCoordPos )
|
|
{
|
|
bool nearLeftBorder = false;
|
|
bool nearRightBorder = false;
|
|
bool nearLowerBorder = false;
|
|
bool nearUpperBorder = false;
|
|
|
|
bool nearLeftInner = false;
|
|
bool nearRightInner = false;
|
|
bool nearLowerInner = false;
|
|
bool nearUpperInner = false;
|
|
|
|
bool withinWidth = false;
|
|
bool withinHeight = false;
|
|
|
|
if ( itemCoordPos.x() >= 0 && itemCoordPos.x() <= rect().width() )
|
|
{
|
|
withinWidth = true;
|
|
}
|
|
if ( itemCoordPos.y() >= 0 && itemCoordPos.y() <= rect().height() )
|
|
{
|
|
withinHeight = true;
|
|
}
|
|
|
|
double borderTolerance = rectHandlerBorderTolerance();
|
|
double innerTolerance = mRotationHandleSize * borderTolerance / mHandleSize;
|
|
|
|
if ( itemCoordPos.x() >= 0 && itemCoordPos.x() < borderTolerance )
|
|
{
|
|
nearLeftBorder = true;
|
|
}
|
|
else if ( isRotationEnabled() && itemCoordPos.x() >= borderTolerance && itemCoordPos.x() < ( borderTolerance + innerTolerance ) )
|
|
{
|
|
nearLeftInner = true;
|
|
}
|
|
if ( itemCoordPos.y() >= 0 && itemCoordPos.y() < borderTolerance )
|
|
{
|
|
nearUpperBorder = true;
|
|
}
|
|
else if ( isRotationEnabled() && itemCoordPos.y() >= borderTolerance && itemCoordPos.y() < ( borderTolerance + innerTolerance ) )
|
|
{
|
|
nearUpperInner = true;
|
|
}
|
|
if ( itemCoordPos.x() <= rect().width() && itemCoordPos.x() > ( rect().width() - borderTolerance ) )
|
|
{
|
|
nearRightBorder = true;
|
|
}
|
|
else if ( isRotationEnabled() && itemCoordPos.x() <= ( rect().width() - borderTolerance ) && itemCoordPos.x() > ( rect().width() - borderTolerance - innerTolerance ) )
|
|
{
|
|
nearRightInner = true;
|
|
}
|
|
if ( itemCoordPos.y() <= rect().height() && itemCoordPos.y() > ( rect().height() - borderTolerance ) )
|
|
{
|
|
nearLowerBorder = true;
|
|
}
|
|
else if ( isRotationEnabled() && itemCoordPos.y() <= ( rect().height() - borderTolerance ) && itemCoordPos.y() > ( rect().height() - borderTolerance - innerTolerance ) )
|
|
{
|
|
nearLowerInner = true;
|
|
}
|
|
|
|
if ( nearLeftBorder && nearUpperBorder )
|
|
{
|
|
return Qgis::MouseHandlesAction::ResizeLeftUp;
|
|
}
|
|
else if ( nearLeftBorder && nearLowerBorder )
|
|
{
|
|
return Qgis::MouseHandlesAction::ResizeLeftDown;
|
|
}
|
|
else if ( nearRightBorder && nearUpperBorder )
|
|
{
|
|
return Qgis::MouseHandlesAction::ResizeRightUp;
|
|
}
|
|
else if ( nearRightBorder && nearLowerBorder )
|
|
{
|
|
return Qgis::MouseHandlesAction::ResizeRightDown;
|
|
}
|
|
else if ( nearLeftBorder && withinHeight )
|
|
{
|
|
return Qgis::MouseHandlesAction::ResizeLeft;
|
|
}
|
|
else if ( nearRightBorder && withinHeight )
|
|
{
|
|
return Qgis::MouseHandlesAction::ResizeRight;
|
|
}
|
|
else if ( nearUpperBorder && withinWidth )
|
|
{
|
|
return Qgis::MouseHandlesAction::ResizeUp;
|
|
}
|
|
else if ( nearLowerBorder && withinWidth )
|
|
{
|
|
return Qgis::MouseHandlesAction::ResizeDown;
|
|
}
|
|
else if ( nearLeftInner && nearUpperInner )
|
|
{
|
|
return Qgis::MouseHandlesAction::RotateLeftUp;
|
|
}
|
|
else if ( nearRightInner && nearUpperInner )
|
|
{
|
|
return Qgis::MouseHandlesAction::RotateRightUp;
|
|
}
|
|
else if ( nearLeftInner && nearLowerInner )
|
|
{
|
|
return Qgis::MouseHandlesAction::RotateLeftDown;
|
|
}
|
|
else if ( nearRightInner && nearLowerInner )
|
|
{
|
|
return Qgis::MouseHandlesAction::RotateRightDown;
|
|
}
|
|
|
|
//find out if cursor position is over a selected item
|
|
QPointF scenePoint = mapToScene( itemCoordPos );
|
|
const QList<QGraphicsItem *> itemsAtCursorPos = sceneItemsAtPoint( scenePoint );
|
|
if ( itemsAtCursorPos.isEmpty() )
|
|
{
|
|
//no items at cursor position
|
|
return Qgis::MouseHandlesAction::SelectItem;
|
|
}
|
|
for ( QGraphicsItem *graphicsItem : itemsAtCursorPos )
|
|
{
|
|
if ( graphicsItem && graphicsItem->isSelected() )
|
|
{
|
|
//cursor is over a selected layout item
|
|
return Qgis::MouseHandlesAction::MoveItem;
|
|
}
|
|
}
|
|
|
|
//default
|
|
return Qgis::MouseHandlesAction::SelectItem;
|
|
}
|
|
|
|
Qgis::MouseHandlesAction QgsGraphicsViewMouseHandles::mouseActionForScenePos( QPointF sceneCoordPos )
|
|
{
|
|
// convert sceneCoordPos to item coordinates
|
|
QPointF itemPos = mapFromScene( sceneCoordPos );
|
|
return mouseActionForPosition( itemPos );
|
|
}
|
|
|
|
bool QgsGraphicsViewMouseHandles::shouldBlockEvent( QInputEvent * ) const
|
|
{
|
|
return mIsDragging || mIsResizing;
|
|
}
|
|
|
|
void QgsGraphicsViewMouseHandles::startMove( QPointF sceneCoordPos )
|
|
{
|
|
//save current cursor position
|
|
mMouseMoveStartPos = sceneCoordPos;
|
|
//save current item geometry
|
|
mBeginMouseEventPos = sceneCoordPos;
|
|
mBeginHandlePos = scenePos();
|
|
mBeginHandleWidth = rect().width();
|
|
mBeginHandleHeight = rect().height();
|
|
mCurrentMouseMoveAction = Qgis::MouseHandlesAction::MoveItem;
|
|
mIsDragging = true;
|
|
hideAlignItems();
|
|
|
|
// Explicitly call grabMouse to ensure the mouse handles receive the subsequent mouse move events.
|
|
if ( mView->scene()->mouseGrabberItem() != this )
|
|
{
|
|
grabMouse();
|
|
}
|
|
}
|
|
|
|
void QgsGraphicsViewMouseHandles::selectedItemSizeChanged()
|
|
{
|
|
if ( !isDragging() && !isResizing() )
|
|
{
|
|
//only required for non-mouse initiated size changes
|
|
updateHandles();
|
|
}
|
|
}
|
|
|
|
void QgsGraphicsViewMouseHandles::selectedItemRotationChanged()
|
|
{
|
|
if ( !isDragging() && !isResizing() )
|
|
{
|
|
//only required for non-mouse initiated rotation changes
|
|
updateHandles();
|
|
}
|
|
}
|
|
|
|
void QgsGraphicsViewMouseHandles::hoverMoveEvent( QGraphicsSceneHoverEvent *event )
|
|
{
|
|
setViewportCursor( cursorForPosition( event->pos() ) );
|
|
}
|
|
|
|
void QgsGraphicsViewMouseHandles::hoverLeaveEvent( QGraphicsSceneHoverEvent *event )
|
|
{
|
|
Q_UNUSED( event )
|
|
setViewportCursor( Qt::ArrowCursor );
|
|
}
|
|
|
|
void QgsGraphicsViewMouseHandles::mousePressEvent( QGraphicsSceneMouseEvent *event )
|
|
{
|
|
if ( event->button() != Qt::LeftButton )
|
|
{
|
|
event->ignore();
|
|
return;
|
|
}
|
|
|
|
//save current cursor position
|
|
mMouseMoveStartPos = event->lastScenePos();
|
|
//save current item geometry
|
|
mBeginMouseEventPos = event->lastScenePos();
|
|
mBeginHandlePos = scenePos();
|
|
mBeginHandleWidth = rect().width();
|
|
mBeginHandleHeight = rect().height();
|
|
//type of mouse move action
|
|
mCurrentMouseMoveAction = mouseActionForPosition( event->pos() );
|
|
|
|
hideAlignItems();
|
|
|
|
switch ( mCurrentMouseMoveAction )
|
|
{
|
|
case Qgis::MouseHandlesAction::MoveItem:
|
|
//moving items
|
|
mIsDragging = true;
|
|
break;
|
|
|
|
case Qgis::MouseHandlesAction::ResizeUp:
|
|
case Qgis::MouseHandlesAction::ResizeDown:
|
|
case Qgis::MouseHandlesAction::ResizeLeft:
|
|
case Qgis::MouseHandlesAction::ResizeRight:
|
|
case Qgis::MouseHandlesAction::ResizeLeftUp:
|
|
case Qgis::MouseHandlesAction::ResizeRightUp:
|
|
case Qgis::MouseHandlesAction::ResizeLeftDown:
|
|
case Qgis::MouseHandlesAction::ResizeRightDown:
|
|
//resizing items
|
|
mIsResizing = true;
|
|
mResizeRect = QRectF( 0, 0, mBeginHandleWidth, mBeginHandleHeight );
|
|
mResizeMoveX = 0;
|
|
mResizeMoveY = 0;
|
|
mCursorOffset = calcCursorEdgeOffset( mMouseMoveStartPos );
|
|
break;
|
|
|
|
case Qgis::MouseHandlesAction::RotateLeftUp:
|
|
case Qgis::MouseHandlesAction::RotateRightUp:
|
|
case Qgis::MouseHandlesAction::RotateLeftDown:
|
|
case Qgis::MouseHandlesAction::RotateRightDown:
|
|
mIsRotating = true;
|
|
mRotationCenter = sceneTransform().map( rect().center() );
|
|
mRotationBegin = std::atan2( mMouseMoveStartPos.y() - mRotationCenter.y(), mMouseMoveStartPos.x() - mRotationCenter.x() ) * 180 / M_PI;
|
|
mRotationCurrent = 0.0;
|
|
break;
|
|
|
|
case Qgis::MouseHandlesAction::SelectItem:
|
|
case Qgis::MouseHandlesAction::NoAction:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void QgsGraphicsViewMouseHandles::resetStatusBar()
|
|
{
|
|
const QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
|
|
int selectedCount = selectedItems.size();
|
|
if ( selectedCount )
|
|
{
|
|
//set status bar message to count of selected items
|
|
showStatusMessage( tr( "%n item(s) selected", nullptr, selectedCount ) );
|
|
}
|
|
else
|
|
{
|
|
//clear status bar message
|
|
showStatusMessage( QString() );
|
|
}
|
|
}
|
|
|
|
void QgsGraphicsViewMouseHandles::mouseMoveEvent( QGraphicsSceneMouseEvent *event )
|
|
{
|
|
if ( isDragging() )
|
|
{
|
|
//currently dragging a selection
|
|
//if shift depressed, constrain movement to horizontal/vertical
|
|
//if control depressed, ignore snapping
|
|
dragMouseMove( event->lastScenePos(), event->modifiers() & Qt::ShiftModifier, event->modifiers() & Qt::ControlModifier );
|
|
}
|
|
else if ( isResizing() )
|
|
{
|
|
//currently resizing a selection
|
|
//lock aspect ratio if shift depressed
|
|
//resize from center if alt depressed
|
|
resizeMouseMove( event->lastScenePos(), event->modifiers() & Qt::ShiftModifier, event->modifiers() & Qt::AltModifier );
|
|
}
|
|
else if ( isRotating() )
|
|
{
|
|
//currently rotating a selection
|
|
//snap to common angles if ctrl is pressed
|
|
rotateMouseMove( event->lastScenePos(), event->modifiers() & Qt::ControlModifier );
|
|
}
|
|
}
|
|
|
|
void QgsGraphicsViewMouseHandles::mouseReleaseEvent( QGraphicsSceneMouseEvent *event )
|
|
{
|
|
if ( event->button() != Qt::LeftButton )
|
|
{
|
|
event->ignore();
|
|
return;
|
|
}
|
|
|
|
if ( mDoubleClickInProgress )
|
|
{
|
|
mDoubleClickInProgress = false;
|
|
event->accept();
|
|
return;
|
|
}
|
|
|
|
// Mouse may have been grabbed from the QgsLayoutViewSelectTool, so we need to release it explicitly
|
|
// otherwise, hover events will not be received
|
|
ungrabMouse();
|
|
|
|
QPointF mouseMoveStopPoint = event->lastScenePos();
|
|
double diffX = mouseMoveStopPoint.x() - mMouseMoveStartPos.x();
|
|
double diffY = mouseMoveStopPoint.y() - mMouseMoveStartPos.y();
|
|
|
|
//it was only a click
|
|
if ( std::fabs( diffX ) < std::numeric_limits<double>::min() && std::fabs( diffY ) < std::numeric_limits<double>::min() )
|
|
{
|
|
mIsDragging = false;
|
|
mIsResizing = false;
|
|
mIsRotating = false;
|
|
update();
|
|
hideAlignItems();
|
|
return;
|
|
}
|
|
|
|
if ( mIsDragging )
|
|
{
|
|
//move selected items
|
|
startMacroCommand( tr( "Move Items" ) );
|
|
|
|
QPointF mEndHandleMovePos = scenePos();
|
|
|
|
double deltaX = mEndHandleMovePos.x() - mBeginHandlePos.x();
|
|
double deltaY = mEndHandleMovePos.y() - mBeginHandlePos.y();
|
|
|
|
//move all selected items
|
|
const QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
|
|
for ( QGraphicsItem *item : selectedItems )
|
|
{
|
|
if ( itemIsLocked( item ) || ( item->flags() & QGraphicsItem::ItemIsSelectable ) == 0 || itemIsGroupMember( item ) )
|
|
{
|
|
//don't move locked items, or grouped items (group takes care of that)
|
|
continue;
|
|
}
|
|
|
|
createItemCommand( item );
|
|
moveItem( item, deltaX, deltaY );
|
|
endItemCommand( item );
|
|
}
|
|
endMacroCommand();
|
|
|
|
mIsDragging = false;
|
|
}
|
|
else if ( mIsResizing )
|
|
{
|
|
//resize selected items
|
|
startMacroCommand( tr( "Resize Items" ) );
|
|
|
|
//resize all selected items
|
|
const QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
|
|
for ( QGraphicsItem *item : selectedItems )
|
|
{
|
|
if ( itemIsLocked( item ) || ( item->flags() & QGraphicsItem::ItemIsSelectable ) == 0 )
|
|
{
|
|
//don't resize locked items or deselectable items (e.g., items which make up an item group)
|
|
continue;
|
|
}
|
|
createItemCommand( item );
|
|
|
|
QRectF thisItemRect;
|
|
if ( selectedItems.size() == 1 )
|
|
{
|
|
//only a single item is selected, so set its size to the final resized mouse handle size
|
|
thisItemRect = mResizeRect;
|
|
}
|
|
else
|
|
{
|
|
//multiple items selected, so each needs to be scaled relatively to the final size of the mouse handles
|
|
thisItemRect = mapRectFromItem( item, itemRect( item ) );
|
|
relativeResizeRect( thisItemRect, QRectF( -mResizeMoveX, -mResizeMoveY, mBeginHandleWidth, mBeginHandleHeight ), mResizeRect );
|
|
}
|
|
|
|
thisItemRect = thisItemRect.normalized();
|
|
QPointF newPos = mapToScene( thisItemRect.topLeft() );
|
|
thisItemRect.moveTopLeft( newPos );
|
|
setItemRect( item, thisItemRect );
|
|
|
|
endItemCommand( item );
|
|
}
|
|
endMacroCommand();
|
|
|
|
mIsResizing = false;
|
|
}
|
|
else if ( mIsRotating )
|
|
{
|
|
const QPointF itemRotationCenter = sceneTransform().map( rect().center() );
|
|
|
|
//move selected items
|
|
startMacroCommand( tr( "Rotate Items" ) );
|
|
|
|
//move all selected items
|
|
const QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
|
|
for ( QGraphicsItem *item : selectedItems )
|
|
{
|
|
if ( itemIsLocked( item ) || ( item->flags() & QGraphicsItem::ItemIsSelectable ) == 0 || itemIsGroupMember( item ) )
|
|
{
|
|
//don't move locked items, or grouped items (group takes care of that)
|
|
continue;
|
|
}
|
|
|
|
const QPointF itemCenter = item->mapToScene( itemRect( item ) ).boundingRect().center();
|
|
|
|
QTransform transform;
|
|
transform.translate( itemRotationCenter.x(), itemRotationCenter.y() );
|
|
transform.rotate( mRotationDelta );
|
|
transform.translate( -itemRotationCenter.x(), -itemRotationCenter.y() );
|
|
const QPointF rotatedItemCenter = transform.map( itemCenter );
|
|
|
|
createItemCommand( item );
|
|
rotateItem( item, mRotationDelta, rotatedItemCenter.x() - itemCenter.x(), rotatedItemCenter.y() - itemCenter.y() );
|
|
endItemCommand( item );
|
|
}
|
|
endMacroCommand();
|
|
|
|
mIsRotating = false;
|
|
}
|
|
|
|
hideAlignItems();
|
|
|
|
//reset default action
|
|
mCurrentMouseMoveAction = Qgis::MouseHandlesAction::MoveItem;
|
|
//redraw handles
|
|
resetTransform();
|
|
updateHandles();
|
|
//reset status bar message
|
|
resetStatusBar();
|
|
}
|
|
|
|
bool QgsGraphicsViewMouseHandles::selectionRotation( double &rotation ) const
|
|
{
|
|
//check if all selected items have same rotation
|
|
QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
|
|
auto itemIter = selectedItems.constBegin();
|
|
|
|
//start with rotation of first selected item
|
|
double firstItemRotation = ( *itemIter )->rotation();
|
|
|
|
//iterate through remaining items, checking if they have same rotation
|
|
for ( ++itemIter; itemIter != selectedItems.constEnd(); ++itemIter )
|
|
{
|
|
if ( !qgsDoubleNear( ( *itemIter )->rotation(), firstItemRotation ) )
|
|
{
|
|
//item has a different rotation, so return false
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//all items have the same rotation, so set the rotation variable and return true
|
|
rotation = firstItemRotation;
|
|
return true;
|
|
}
|
|
|
|
void QgsGraphicsViewMouseHandles::updateHandles()
|
|
{
|
|
//recalculate size and position of handle item
|
|
|
|
//first check to see if any items are selected
|
|
QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
|
|
if ( !selectedItems.isEmpty() )
|
|
{
|
|
//one or more items are selected, get bounds of all selected items
|
|
|
|
//update rotation of handle object
|
|
double rotation;
|
|
if ( selectionRotation( rotation ) )
|
|
{
|
|
//all items share a common rotation value, so we rotate the mouse handles to match
|
|
setRotation( rotation );
|
|
}
|
|
else
|
|
{
|
|
//items have varying rotation values - we can't rotate the mouse handles to match
|
|
setRotation( 0 );
|
|
}
|
|
|
|
//get bounds of all selected items
|
|
QRectF newHandleBounds = selectionBounds();
|
|
|
|
//update size and position of handle object
|
|
setRect( 0, 0, newHandleBounds.width(), newHandleBounds.height() );
|
|
setPos( mapToScene( newHandleBounds.topLeft() ) );
|
|
|
|
show();
|
|
}
|
|
else
|
|
{
|
|
//no items selected, hide handles
|
|
hide();
|
|
}
|
|
//force redraw
|
|
update();
|
|
}
|
|
|
|
void QgsGraphicsViewMouseHandles::rotateMouseMove( QPointF currentPosition, bool snapToCommonAngles )
|
|
{
|
|
if ( !scene() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
mRotationCurrent = std::atan2( currentPosition.y() - mRotationCenter.y(), currentPosition.x() - mRotationCenter.x() ) * 180 / M_PI;
|
|
mRotationDelta = mRotationCurrent - mRotationBegin;
|
|
if ( snapToCommonAngles )
|
|
{
|
|
const double commonAngles = 15;
|
|
double snappedRotationDelta = std::floor( std::abs( mRotationDelta ) / commonAngles ) * commonAngles;
|
|
if ( std::abs( std::fmod( mRotationDelta, commonAngles ) ) >= 10 )
|
|
{
|
|
snappedRotationDelta += commonAngles;
|
|
}
|
|
mRotationDelta = mRotationDelta >= 0 ? snappedRotationDelta : -snappedRotationDelta;
|
|
}
|
|
|
|
const double itemRotationRadian = rotation() * M_PI / 180;
|
|
const double deltaX = ( rect().width() / 2 ) * cos( itemRotationRadian ) - ( rect().height() / 2 ) * sin( itemRotationRadian );
|
|
const double deltaY = ( rect().width() / 2 ) * sin( itemRotationRadian ) + ( rect().height() / 2 ) * cos( itemRotationRadian );
|
|
|
|
QTransform rotateTransform;
|
|
rotateTransform.translate( deltaX, deltaY );
|
|
rotateTransform.rotate( mRotationDelta );
|
|
rotateTransform.translate( -deltaX, -deltaY );
|
|
setTransform( rotateTransform );
|
|
|
|
//show current selection rotation in status bar
|
|
showStatusMessage( tr( "rotation: %1°" ).arg( QString::number( mRotationDelta, 'f', 2 ) ) );
|
|
|
|
return;
|
|
}
|
|
|
|
void QgsGraphicsViewMouseHandles::dragMouseMove( QPointF currentPosition, bool lockMovement, bool preventSnap )
|
|
{
|
|
if ( !scene() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//calculate total amount of mouse movement since drag began
|
|
double moveX = currentPosition.x() - mBeginMouseEventPos.x();
|
|
double moveY = currentPosition.y() - mBeginMouseEventPos.y();
|
|
|
|
//find target position before snapping (in scene coordinates)
|
|
QPointF upperLeftPoint( mBeginHandlePos.x() + moveX, mBeginHandlePos.y() + moveY );
|
|
|
|
QPointF snappedLeftPoint;
|
|
|
|
//no snapping for rotated items for now
|
|
if ( !preventSnap && qgsDoubleNear( rotation(), 0.0 ) )
|
|
{
|
|
//snap to grid and guides
|
|
snappedLeftPoint = snapPoint( upperLeftPoint, Item );
|
|
}
|
|
else
|
|
{
|
|
//no snapping
|
|
snappedLeftPoint = upperLeftPoint;
|
|
hideAlignItems();
|
|
}
|
|
|
|
//calculate total shift for item from beginning of drag operation to current position
|
|
double moveRectX = snappedLeftPoint.x() - mBeginHandlePos.x();
|
|
double moveRectY = snappedLeftPoint.y() - mBeginHandlePos.y();
|
|
|
|
if ( lockMovement )
|
|
{
|
|
//constrained (shift) moving should lock to horizontal/vertical movement
|
|
//reset the smaller of the x/y movements
|
|
if ( std::fabs( moveRectX ) <= std::fabs( moveRectY ) )
|
|
{
|
|
moveRectX = 0;
|
|
}
|
|
else
|
|
{
|
|
moveRectY = 0;
|
|
}
|
|
}
|
|
|
|
//shift handle item to new position
|
|
QTransform moveTransform;
|
|
moveTransform.translate( moveRectX, moveRectY );
|
|
setTransform( moveTransform );
|
|
|
|
const QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
|
|
for ( QGraphicsItem *item : selectedItems )
|
|
{
|
|
previewItemMove( item, moveRectX, moveRectY );
|
|
}
|
|
//show current displacement of selection in status bar
|
|
showStatusMessage( tr( "dx: %1 mm dy: %2 mm" ).arg( moveRectX ).arg( moveRectY ) );
|
|
}
|
|
|
|
void QgsGraphicsViewMouseHandles::resizeMouseMove( QPointF currentPosition, bool lockRatio, bool fromCenter )
|
|
{
|
|
if ( !scene() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
double mx = 0.0, my = 0.0, rx = 0.0, ry = 0.0;
|
|
|
|
QPointF beginMousePos;
|
|
QPointF finalPosition;
|
|
if ( qgsDoubleNear( rotation(), 0.0 ) )
|
|
{
|
|
//snapping only occurs if handles are not rotated for now
|
|
|
|
bool snapVertical = mCurrentMouseMoveAction == Qgis::MouseHandlesAction::ResizeLeft || mCurrentMouseMoveAction == Qgis::MouseHandlesAction::ResizeRight || mCurrentMouseMoveAction == Qgis::MouseHandlesAction::ResizeLeftUp || mCurrentMouseMoveAction == Qgis::MouseHandlesAction::ResizeRightUp || mCurrentMouseMoveAction == Qgis::MouseHandlesAction::ResizeLeftDown || mCurrentMouseMoveAction == Qgis::MouseHandlesAction::ResizeRightDown;
|
|
|
|
bool snapHorizontal = mCurrentMouseMoveAction == Qgis::MouseHandlesAction::ResizeUp || mCurrentMouseMoveAction == Qgis::MouseHandlesAction::ResizeDown || mCurrentMouseMoveAction == Qgis::MouseHandlesAction::ResizeLeftUp || mCurrentMouseMoveAction == Qgis::MouseHandlesAction::ResizeRightUp || mCurrentMouseMoveAction == Qgis::MouseHandlesAction::ResizeLeftDown || mCurrentMouseMoveAction == Qgis::MouseHandlesAction::ResizeRightDown;
|
|
|
|
//subtract cursor edge offset from begin mouse event and current cursor position, so that snapping occurs to edge of mouse handles
|
|
//rather then cursor position
|
|
beginMousePos = mapFromScene( QPointF( mBeginMouseEventPos.x() - mCursorOffset.width(), mBeginMouseEventPos.y() - mCursorOffset.height() ) );
|
|
QPointF snappedPosition = snapPoint( QPointF( currentPosition.x() - mCursorOffset.width(), currentPosition.y() - mCursorOffset.height() ), Point, snapHorizontal, snapVertical );
|
|
finalPosition = mapFromScene( snappedPosition );
|
|
}
|
|
else
|
|
{
|
|
//no snapping for rotated items for now
|
|
beginMousePos = mapFromScene( mBeginMouseEventPos );
|
|
finalPosition = mapFromScene( currentPosition );
|
|
}
|
|
|
|
double diffX = finalPosition.x() - beginMousePos.x();
|
|
double diffY = finalPosition.y() - beginMousePos.y();
|
|
|
|
double ratio = 0;
|
|
if ( lockRatio && !qgsDoubleNear( mBeginHandleHeight, 0.0 ) )
|
|
{
|
|
ratio = mBeginHandleWidth / mBeginHandleHeight;
|
|
}
|
|
|
|
switch ( mCurrentMouseMoveAction )
|
|
{
|
|
//vertical resize
|
|
case Qgis::MouseHandlesAction::ResizeUp:
|
|
{
|
|
if ( ratio )
|
|
{
|
|
diffX = ( ( mBeginHandleHeight - diffY ) * ratio ) - mBeginHandleWidth;
|
|
mx = -diffX / 2;
|
|
my = diffY;
|
|
rx = diffX;
|
|
ry = -diffY;
|
|
}
|
|
else
|
|
{
|
|
mx = 0;
|
|
my = diffY;
|
|
rx = 0;
|
|
ry = -diffY;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Qgis::MouseHandlesAction::ResizeDown:
|
|
{
|
|
if ( ratio )
|
|
{
|
|
diffX = ( ( mBeginHandleHeight + diffY ) * ratio ) - mBeginHandleWidth;
|
|
mx = -diffX / 2;
|
|
my = 0;
|
|
rx = diffX;
|
|
ry = diffY;
|
|
}
|
|
else
|
|
{
|
|
mx = 0;
|
|
my = 0;
|
|
rx = 0;
|
|
ry = diffY;
|
|
}
|
|
break;
|
|
}
|
|
|
|
//horizontal resize
|
|
case Qgis::MouseHandlesAction::ResizeLeft:
|
|
{
|
|
if ( ratio )
|
|
{
|
|
diffY = ( ( mBeginHandleWidth - diffX ) / ratio ) - mBeginHandleHeight;
|
|
mx = diffX;
|
|
my = -diffY / 2;
|
|
rx = -diffX;
|
|
ry = diffY;
|
|
}
|
|
else
|
|
{
|
|
mx = diffX, my = 0;
|
|
rx = -diffX;
|
|
ry = 0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Qgis::MouseHandlesAction::ResizeRight:
|
|
{
|
|
if ( ratio )
|
|
{
|
|
diffY = ( ( mBeginHandleWidth + diffX ) / ratio ) - mBeginHandleHeight;
|
|
mx = 0;
|
|
my = -diffY / 2;
|
|
rx = diffX;
|
|
ry = diffY;
|
|
}
|
|
else
|
|
{
|
|
mx = 0;
|
|
my = 0;
|
|
rx = diffX, ry = 0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
//diagonal resize
|
|
case Qgis::MouseHandlesAction::ResizeLeftUp:
|
|
{
|
|
if ( ratio )
|
|
{
|
|
//ratio locked resize
|
|
if ( ( mBeginHandleWidth - diffX ) / ( mBeginHandleHeight - diffY ) > ratio )
|
|
{
|
|
diffX = mBeginHandleWidth - ( ( mBeginHandleHeight - diffY ) * ratio );
|
|
}
|
|
else
|
|
{
|
|
diffY = mBeginHandleHeight - ( ( mBeginHandleWidth - diffX ) / ratio );
|
|
}
|
|
}
|
|
mx = diffX, my = diffY;
|
|
rx = -diffX;
|
|
ry = -diffY;
|
|
break;
|
|
}
|
|
|
|
case Qgis::MouseHandlesAction::ResizeRightDown:
|
|
{
|
|
if ( ratio )
|
|
{
|
|
//ratio locked resize
|
|
if ( ( mBeginHandleWidth + diffX ) / ( mBeginHandleHeight + diffY ) > ratio )
|
|
{
|
|
diffX = ( ( mBeginHandleHeight + diffY ) * ratio ) - mBeginHandleWidth;
|
|
}
|
|
else
|
|
{
|
|
diffY = ( ( mBeginHandleWidth + diffX ) / ratio ) - mBeginHandleHeight;
|
|
}
|
|
}
|
|
mx = 0;
|
|
my = 0;
|
|
rx = diffX, ry = diffY;
|
|
break;
|
|
}
|
|
|
|
case Qgis::MouseHandlesAction::ResizeRightUp:
|
|
{
|
|
if ( ratio )
|
|
{
|
|
//ratio locked resize
|
|
if ( ( mBeginHandleWidth + diffX ) / ( mBeginHandleHeight - diffY ) > ratio )
|
|
{
|
|
diffX = ( ( mBeginHandleHeight - diffY ) * ratio ) - mBeginHandleWidth;
|
|
}
|
|
else
|
|
{
|
|
diffY = mBeginHandleHeight - ( ( mBeginHandleWidth + diffX ) / ratio );
|
|
}
|
|
}
|
|
mx = 0;
|
|
my = diffY, rx = diffX, ry = -diffY;
|
|
break;
|
|
}
|
|
|
|
case Qgis::MouseHandlesAction::ResizeLeftDown:
|
|
{
|
|
if ( ratio )
|
|
{
|
|
//ratio locked resize
|
|
if ( ( mBeginHandleWidth - diffX ) / ( mBeginHandleHeight + diffY ) > ratio )
|
|
{
|
|
diffX = mBeginHandleWidth - ( ( mBeginHandleHeight + diffY ) * ratio );
|
|
}
|
|
else
|
|
{
|
|
diffY = ( ( mBeginHandleWidth - diffX ) / ratio ) - mBeginHandleHeight;
|
|
}
|
|
}
|
|
mx = diffX, my = 0;
|
|
rx = -diffX;
|
|
ry = diffY;
|
|
break;
|
|
}
|
|
|
|
case Qgis::MouseHandlesAction::RotateLeftUp:
|
|
case Qgis::MouseHandlesAction::RotateRightUp:
|
|
case Qgis::MouseHandlesAction::RotateLeftDown:
|
|
case Qgis::MouseHandlesAction::RotateRightDown:
|
|
case Qgis::MouseHandlesAction::MoveItem:
|
|
case Qgis::MouseHandlesAction::SelectItem:
|
|
case Qgis::MouseHandlesAction::NoAction:
|
|
break;
|
|
}
|
|
|
|
//resizing from center of objects?
|
|
if ( fromCenter )
|
|
{
|
|
my = -ry;
|
|
mx = -rx;
|
|
ry = 2 * ry;
|
|
rx = 2 * rx;
|
|
}
|
|
|
|
//update selection handle rectangle
|
|
|
|
//make sure selection handle size rectangle is normalized (ie, left coord < right coord)
|
|
mResizeMoveX = mBeginHandleWidth + rx > 0 ? mx : mx + mBeginHandleWidth + rx;
|
|
mResizeMoveY = mBeginHandleHeight + ry > 0 ? my : my + mBeginHandleHeight + ry;
|
|
|
|
//calculate movement in scene coordinates
|
|
QLineF translateLine = QLineF( 0, 0, mResizeMoveX, mResizeMoveY );
|
|
translateLine.setAngle( translateLine.angle() - rotation() );
|
|
QPointF sceneTranslate = translateLine.p2();
|
|
|
|
//move selection handles
|
|
QTransform itemTransform;
|
|
itemTransform.translate( sceneTranslate.x(), sceneTranslate.y() );
|
|
setTransform( itemTransform );
|
|
|
|
//handle non-normalised resizes - e.g., dragging the left handle so far to the right that it's past the right handle
|
|
if ( mBeginHandleWidth + rx >= 0 && mBeginHandleHeight + ry >= 0 )
|
|
{
|
|
mResizeRect = QRectF( 0, 0, mBeginHandleWidth + rx, mBeginHandleHeight + ry );
|
|
}
|
|
else if ( mBeginHandleHeight + ry >= 0 )
|
|
{
|
|
mResizeRect = QRectF( QPointF( -( mBeginHandleWidth + rx ), 0 ), QPointF( 0, mBeginHandleHeight + ry ) );
|
|
}
|
|
else if ( mBeginHandleWidth + rx >= 0 )
|
|
{
|
|
mResizeRect = QRectF( QPointF( 0, -( mBeginHandleHeight + ry ) ), QPointF( mBeginHandleWidth + rx, 0 ) );
|
|
}
|
|
else
|
|
{
|
|
mResizeRect = QRectF( QPointF( -( mBeginHandleWidth + rx ), -( mBeginHandleHeight + ry ) ), QPointF( 0, 0 ) );
|
|
}
|
|
|
|
const QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
|
|
QRectF newHandleBounds;
|
|
for ( QGraphicsItem *item : selectedItems )
|
|
{
|
|
//get stored item bounds in mouse handle item's coordinate system
|
|
QRectF thisItemRect = mapRectFromScene( storedItemRect( item ) );
|
|
//now, resize it relative to the current resized dimensions of the mouse handles
|
|
relativeResizeRect( thisItemRect, QRectF( -mResizeMoveX, -mResizeMoveY, mBeginHandleWidth, mBeginHandleHeight ), mResizeRect );
|
|
|
|
thisItemRect = mapRectFromScene( previewSetItemRect( item, mapRectToScene( thisItemRect ) ) );
|
|
newHandleBounds = newHandleBounds.isValid() ? newHandleBounds.united( thisItemRect ) : thisItemRect;
|
|
}
|
|
|
|
setRect( newHandleBounds.isValid() ? newHandleBounds : QRectF( 0, 0, std::fabs( mBeginHandleWidth + rx ), std::fabs( mBeginHandleHeight + ry ) ) );
|
|
|
|
//show current size of selection in status bar
|
|
showStatusMessage( tr( "width: %1 mm height: %2 mm" ).arg( rect().width() ).arg( rect().height() ) );
|
|
}
|
|
|
|
void QgsGraphicsViewMouseHandles::setHandleSize( double size )
|
|
{
|
|
mHandleSize = size;
|
|
}
|
|
|
|
void QgsGraphicsViewMouseHandles::mouseDoubleClickEvent( QGraphicsSceneMouseEvent *event )
|
|
{
|
|
Q_UNUSED( event )
|
|
|
|
mDoubleClickInProgress = true;
|
|
}
|
|
|
|
QSizeF QgsGraphicsViewMouseHandles::calcCursorEdgeOffset( QPointF cursorPos )
|
|
{
|
|
//find offset between cursor position and actual edge of item
|
|
QPointF sceneMousePos = mapFromScene( cursorPos );
|
|
|
|
switch ( mCurrentMouseMoveAction )
|
|
{
|
|
//vertical resize
|
|
case Qgis::MouseHandlesAction::ResizeUp:
|
|
return QSizeF( 0, sceneMousePos.y() );
|
|
|
|
case Qgis::MouseHandlesAction::ResizeDown:
|
|
return QSizeF( 0, sceneMousePos.y() - rect().height() );
|
|
|
|
//horizontal resize
|
|
case Qgis::MouseHandlesAction::ResizeLeft:
|
|
return QSizeF( sceneMousePos.x(), 0 );
|
|
|
|
case Qgis::MouseHandlesAction::ResizeRight:
|
|
return QSizeF( sceneMousePos.x() - rect().width(), 0 );
|
|
|
|
//diagonal resize
|
|
case Qgis::MouseHandlesAction::ResizeLeftUp:
|
|
return QSizeF( sceneMousePos.x(), sceneMousePos.y() );
|
|
|
|
case Qgis::MouseHandlesAction::ResizeRightDown:
|
|
return QSizeF( sceneMousePos.x() - rect().width(), sceneMousePos.y() - rect().height() );
|
|
|
|
case Qgis::MouseHandlesAction::ResizeRightUp:
|
|
return QSizeF( sceneMousePos.x() - rect().width(), sceneMousePos.y() );
|
|
|
|
case Qgis::MouseHandlesAction::ResizeLeftDown:
|
|
return QSizeF( sceneMousePos.x(), sceneMousePos.y() - rect().height() );
|
|
|
|
case Qgis::MouseHandlesAction::RotateLeftUp:
|
|
case Qgis::MouseHandlesAction::RotateRightUp:
|
|
case Qgis::MouseHandlesAction::RotateLeftDown:
|
|
case Qgis::MouseHandlesAction::RotateRightDown:
|
|
case Qgis::MouseHandlesAction::MoveItem:
|
|
case Qgis::MouseHandlesAction::SelectItem:
|
|
case Qgis::MouseHandlesAction::NoAction:
|
|
return QSizeF();
|
|
}
|
|
|
|
return QSizeF();
|
|
}
|
|
|
|
QRectF QgsGraphicsViewMouseHandles::selectionBounds() const
|
|
{
|
|
//calculate bounds of all currently selected items in mouse handle coordinate system
|
|
const QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
|
|
auto itemIter = selectedItems.constBegin();
|
|
|
|
//start with handle bounds of first selected item
|
|
QRectF bounds = mapFromItem( ( *itemIter ), itemRect( *itemIter ) ).boundingRect();
|
|
|
|
//iterate through remaining items, expanding the bounds as required
|
|
for ( ++itemIter; itemIter != selectedItems.constEnd(); ++itemIter )
|
|
{
|
|
bounds = bounds.united( mapFromItem( ( *itemIter ), itemRect( *itemIter ) ).boundingRect() );
|
|
}
|
|
|
|
return bounds;
|
|
}
|
|
|
|
void QgsGraphicsViewMouseHandles::relativeResizeRect( QRectF &rectToResize, const QRectF &boundsBefore, const QRectF &boundsAfter )
|
|
{
|
|
//linearly scale rectToResize relative to the scaling from boundsBefore to boundsAfter
|
|
double left = relativePosition( rectToResize.left(), boundsBefore.left(), boundsBefore.right(), boundsAfter.left(), boundsAfter.right() );
|
|
double right = relativePosition( rectToResize.right(), boundsBefore.left(), boundsBefore.right(), boundsAfter.left(), boundsAfter.right() );
|
|
double top = relativePosition( rectToResize.top(), boundsBefore.top(), boundsBefore.bottom(), boundsAfter.top(), boundsAfter.bottom() );
|
|
double bottom = relativePosition( rectToResize.bottom(), boundsBefore.top(), boundsBefore.bottom(), boundsAfter.top(), boundsAfter.bottom() );
|
|
|
|
rectToResize.setRect( left, top, right - left, bottom - top );
|
|
}
|
|
|
|
double QgsGraphicsViewMouseHandles::relativePosition( double position, double beforeMin, double beforeMax, double afterMin, double afterMax )
|
|
{
|
|
//calculate parameters for linear scale between before and after ranges
|
|
double m = ( afterMax - afterMin ) / ( beforeMax - beforeMin );
|
|
double c = afterMin - ( beforeMin * m );
|
|
|
|
//return linearly scaled position
|
|
return m * position + c;
|
|
}
|
|
|
|
///@endcond PRIVATE
|