Compare commits

...

13 Commits

Author SHA1 Message Date
Loïc Bartoletti
841a8269cb
Merge 80f277f91ffeda44915a7b78a369db1f43ca53ee into b927df884feb840b67724af5c82c8088a9d20bfe 2025-07-02 13:06:02 +02:00
Loïc Bartoletti
b927df884f
Merge pull request #62484 from jef-n/fix-62478
fix #62478 (followup eac401c009)
2025-07-02 06:09:23 +02:00
qgis-bot
fe299930cd auto sipify 🍺 2025-07-01 23:40:57 +00:00
Mathieu Pellerin
8c7edbeec2 Rename rotation enum keys 2025-07-02 11:38:05 +12:00
Mathieu Pellerin
ce2c8ac21f Address review 2025-07-02 11:38:05 +12:00
Mathieu Pellerin
faf4afcba7 Fix case warning 2025-07-02 11:38:05 +12:00
Mathieu Pellerin
4bad76d876 Where did my pre-commit hook go? 2025-07-02 11:38:05 +12:00
Mathieu Pellerin
1f6c1277dd Lock rotation handle UI/UX behind an enabled boolean 2025-07-02 11:38:05 +12:00
Mathieu Pellerin
ae463d8b75 Implement a ctrl modifier to snap to common angles when rotating item(s) 2025-07-02 11:38:05 +12:00
Mathieu Pellerin
fb0d20942d Show rotation (delta) value in status bar 2025-07-02 11:38:05 +12:00
Mathieu Pellerin
1680a6cd2e [layouts] Brand new layout item rotation handles 2025-07-02 11:38:05 +12:00
Juergen E. Fischer
6fd32d92fd fix #62478 (followup eac401c009) 2025-07-01 17:54:58 +02:00
Loïc Bartoletti
80f277f91f feat(Transect): Add option to generate a transect at a fixed distance (ignore existing vertices) 2025-04-17 21:39:45 +02:00
14 changed files with 459 additions and 59 deletions

View File

@ -11505,6 +11505,10 @@ Qgis.MouseHandlesAction.ResizeLeftUp.__doc__ = "Resize left up (Top left handle)
Qgis.MouseHandlesAction.ResizeRightUp.__doc__ = "Resize right up (Top right handle)" Qgis.MouseHandlesAction.ResizeRightUp.__doc__ = "Resize right up (Top right handle)"
Qgis.MouseHandlesAction.ResizeLeftDown.__doc__ = "Resize left down (Bottom left handle)" Qgis.MouseHandlesAction.ResizeLeftDown.__doc__ = "Resize left down (Bottom left handle)"
Qgis.MouseHandlesAction.ResizeRightDown.__doc__ = "Resize right down (Bottom right handle)" Qgis.MouseHandlesAction.ResizeRightDown.__doc__ = "Resize right down (Bottom right handle)"
Qgis.MouseHandlesAction.RotateTopLeft.__doc__ = "Rotate from top left handle. \n.. versionadded:: 4.0"
Qgis.MouseHandlesAction.RotateTopRight.__doc__ = "Rotate from top right handle. \n.. versionadded:: 4.0"
Qgis.MouseHandlesAction.RotateBottomLeft.__doc__ = "Rotate from bottom left handle. \n.. versionadded:: 4.0"
Qgis.MouseHandlesAction.RotateBottomRight.__doc__ = "Rotate right bottom right handle. \n.. versionadded:: 4.0"
Qgis.MouseHandlesAction.SelectItem.__doc__ = "Select item" Qgis.MouseHandlesAction.SelectItem.__doc__ = "Select item"
Qgis.MouseHandlesAction.NoAction.__doc__ = "No action" Qgis.MouseHandlesAction.NoAction.__doc__ = "No action"
Qgis.MouseHandlesAction.__doc__ = """Action to be performed by the mouse handles Qgis.MouseHandlesAction.__doc__ = """Action to be performed by the mouse handles
@ -11520,6 +11524,22 @@ Qgis.MouseHandlesAction.__doc__ = """Action to be performed by the mouse handles
* ``ResizeRightUp``: Resize right up (Top right handle) * ``ResizeRightUp``: Resize right up (Top right handle)
* ``ResizeLeftDown``: Resize left down (Bottom left handle) * ``ResizeLeftDown``: Resize left down (Bottom left handle)
* ``ResizeRightDown``: Resize right down (Bottom right handle) * ``ResizeRightDown``: Resize right down (Bottom right handle)
* ``RotateTopLeft``: Rotate from top left handle.
.. versionadded:: 4.0
* ``RotateTopRight``: Rotate from top right handle.
.. versionadded:: 4.0
* ``RotateBottomLeft``: Rotate from bottom left handle.
.. versionadded:: 4.0
* ``RotateBottomRight``: Rotate right bottom right handle.
.. versionadded:: 4.0
* ``SelectItem``: Select item * ``SelectItem``: Select item
* ``NoAction``: No action * ``NoAction``: No action

View File

@ -3347,6 +3347,10 @@ The development version
ResizeRightUp, ResizeRightUp,
ResizeLeftDown, ResizeLeftDown,
ResizeRightDown, ResizeRightDown,
RotateTopLeft,
RotateTopRight,
RotateBottomLeft,
RotateBottomRight,
SelectItem, SelectItem,
NoAction NoAction
}; };

View File

@ -1,14 +1,14 @@
Qgis.defaultProjectScales: src/core/qgis.h#L6068 Qgis.defaultProjectScales: src/core/qgis.h#L6072
Qgis.devVersion: src/core/qgis.h#L89 Qgis.devVersion: src/core/qgis.h#L89
Qgis.geoNone: src/core/qgis.h#L6113 Qgis.geoNone: src/core/qgis.h#L6117
Qgis.geoProj4: src/core/qgis.h#L6143 Qgis.geoProj4: src/core/qgis.h#L6147
Qgis.geoWkt: src/core/qgis.h#L6134 Qgis.geoWkt: src/core/qgis.h#L6138
Qgis.geographicCrsAuthId: src/core/qgis.h#L6123 Qgis.geographicCrsAuthId: src/core/qgis.h#L6127
Qgis.geosVersion: src/core/qgis.h#L6103 Qgis.geosVersion: src/core/qgis.h#L6107
Qgis.geosVersionInt: src/core/qgis.h#L6075 Qgis.geosVersionInt: src/core/qgis.h#L6079
Qgis.geosVersionMajor: src/core/qgis.h#L6082 Qgis.geosVersionMajor: src/core/qgis.h#L6086
Qgis.geosVersionMinor: src/core/qgis.h#L6089 Qgis.geosVersionMinor: src/core/qgis.h#L6093
Qgis.geosVersionPatch: src/core/qgis.h#L6096 Qgis.geosVersionPatch: src/core/qgis.h#L6100
Qgis.releaseName: src/core/qgis.h#L79 Qgis.releaseName: src/core/qgis.h#L79
Qgis.version: src/core/qgis.h#L65 Qgis.version: src/core/qgis.h#L65
Qgis.versionInt: src/core/qgis.h#L72 Qgis.versionInt: src/core/qgis.h#L72

View File

@ -11411,6 +11411,10 @@ Qgis.MouseHandlesAction.ResizeLeftUp.__doc__ = "Resize left up (Top left handle)
Qgis.MouseHandlesAction.ResizeRightUp.__doc__ = "Resize right up (Top right handle)" Qgis.MouseHandlesAction.ResizeRightUp.__doc__ = "Resize right up (Top right handle)"
Qgis.MouseHandlesAction.ResizeLeftDown.__doc__ = "Resize left down (Bottom left handle)" Qgis.MouseHandlesAction.ResizeLeftDown.__doc__ = "Resize left down (Bottom left handle)"
Qgis.MouseHandlesAction.ResizeRightDown.__doc__ = "Resize right down (Bottom right handle)" Qgis.MouseHandlesAction.ResizeRightDown.__doc__ = "Resize right down (Bottom right handle)"
Qgis.MouseHandlesAction.RotateTopLeft.__doc__ = "Rotate from top left handle. \n.. versionadded:: 4.0"
Qgis.MouseHandlesAction.RotateTopRight.__doc__ = "Rotate from top right handle. \n.. versionadded:: 4.0"
Qgis.MouseHandlesAction.RotateBottomLeft.__doc__ = "Rotate from bottom left handle. \n.. versionadded:: 4.0"
Qgis.MouseHandlesAction.RotateBottomRight.__doc__ = "Rotate right bottom right handle. \n.. versionadded:: 4.0"
Qgis.MouseHandlesAction.SelectItem.__doc__ = "Select item" Qgis.MouseHandlesAction.SelectItem.__doc__ = "Select item"
Qgis.MouseHandlesAction.NoAction.__doc__ = "No action" Qgis.MouseHandlesAction.NoAction.__doc__ = "No action"
Qgis.MouseHandlesAction.__doc__ = """Action to be performed by the mouse handles Qgis.MouseHandlesAction.__doc__ = """Action to be performed by the mouse handles
@ -11426,6 +11430,22 @@ Qgis.MouseHandlesAction.__doc__ = """Action to be performed by the mouse handles
* ``ResizeRightUp``: Resize right up (Top right handle) * ``ResizeRightUp``: Resize right up (Top right handle)
* ``ResizeLeftDown``: Resize left down (Bottom left handle) * ``ResizeLeftDown``: Resize left down (Bottom left handle)
* ``ResizeRightDown``: Resize right down (Bottom right handle) * ``ResizeRightDown``: Resize right down (Bottom right handle)
* ``RotateTopLeft``: Rotate from top left handle.
.. versionadded:: 4.0
* ``RotateTopRight``: Rotate from top right handle.
.. versionadded:: 4.0
* ``RotateBottomLeft``: Rotate from bottom left handle.
.. versionadded:: 4.0
* ``RotateBottomRight``: Rotate right bottom right handle.
.. versionadded:: 4.0
* ``SelectItem``: Select item * ``SelectItem``: Select item
* ``NoAction``: No action * ``NoAction``: No action

View File

@ -3347,6 +3347,10 @@ The development version
ResizeRightUp, ResizeRightUp,
ResizeLeftDown, ResizeLeftDown,
ResizeRightDown, ResizeRightDown,
RotateTopLeft,
RotateTopRight,
RotateBottomLeft,
RotateBottomRight,
SelectItem, SelectItem,
NoAction NoAction
}; };

View File

@ -1,14 +1,14 @@
Qgis.defaultProjectScales: src/core/qgis.h#L6068 Qgis.defaultProjectScales: src/core/qgis.h#L6072
Qgis.devVersion: src/core/qgis.h#L89 Qgis.devVersion: src/core/qgis.h#L89
Qgis.geoNone: src/core/qgis.h#L6113 Qgis.geoNone: src/core/qgis.h#L6117
Qgis.geoProj4: src/core/qgis.h#L6143 Qgis.geoProj4: src/core/qgis.h#L6147
Qgis.geoWkt: src/core/qgis.h#L6134 Qgis.geoWkt: src/core/qgis.h#L6138
Qgis.geographicCrsAuthId: src/core/qgis.h#L6123 Qgis.geographicCrsAuthId: src/core/qgis.h#L6127
Qgis.geosVersion: src/core/qgis.h#L6103 Qgis.geosVersion: src/core/qgis.h#L6107
Qgis.geosVersionInt: src/core/qgis.h#L6075 Qgis.geosVersionInt: src/core/qgis.h#L6079
Qgis.geosVersionMajor: src/core/qgis.h#L6082 Qgis.geosVersionMajor: src/core/qgis.h#L6086
Qgis.geosVersionMinor: src/core/qgis.h#L6089 Qgis.geosVersionMinor: src/core/qgis.h#L6093
Qgis.geosVersionPatch: src/core/qgis.h#L6096 Qgis.geosVersionPatch: src/core/qgis.h#L6100
Qgis.releaseName: src/core/qgis.h#L79 Qgis.releaseName: src/core/qgis.h#L79
Qgis.version: src/core/qgis.h#L65 Qgis.version: src/core/qgis.h#L65
Qgis.versionInt: src/core/qgis.h#L72 Qgis.versionInt: src/core/qgis.h#L72

View File

@ -658,6 +658,9 @@ class Repositories(QObject):
.text() .text()
.strip() .strip()
) )
supports_qt6 = pluginNodes.item(i).firstChildElement(
"supports_qt6"
).text().strip().upper() in ["TRUE", "YES"]
if not qgisMaximumVersion: if not qgisMaximumVersion:
if qgisMinimumVersion[0] == "3" and supports_qt6: if qgisMinimumVersion[0] == "3" and supports_qt6:
qgisMaximumVersion = "4.99" qgisMaximumVersion = "4.99"

View File

@ -62,6 +62,11 @@ void QgsTransectAlgorithm::initAlgorithm( const QVariantMap & )
addParameter( angle.release() ); addParameter( angle.release() );
addParameter( new QgsProcessingParameterEnum( QStringLiteral( "SIDE" ), QObject::tr( "Side to create the transects" ), QStringList() << QObject::tr( "Left" ) << QObject::tr( "Right" ) << QObject::tr( "Both" ), false ) ); addParameter( new QgsProcessingParameterEnum( QStringLiteral( "SIDE" ), QObject::tr( "Side to create the transects" ), QStringList() << QObject::tr( "Left" ) << QObject::tr( "Right" ) << QObject::tr( "Both" ), false ) );
addParameter( new QgsProcessingParameterNumber( QStringLiteral( "INTERVAL" ), QObject::tr( "Fixed sampling interval" ), Qgis::ProcessingNumberParameterType::Double, 10.0, false, 0 ) );
addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "FIXED_DISTANCE" ), QObject::tr( "Use fixed interval sampling (ignore original vertices)" ), false ) );
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Transect" ), Qgis::ProcessingSourceType::VectorLine ) ); addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Transect" ), Qgis::ProcessingSourceType::VectorLine ) );
} }
@ -109,6 +114,11 @@ QVariantMap QgsTransectAlgorithm::processAlgorithm( const QVariantMap &parameter
if ( dynamicLength ) if ( dynamicLength )
lengthProperty = parameters.value( QStringLiteral( "LENGTH" ) ).value<QgsProperty>(); lengthProperty = parameters.value( QStringLiteral( "LENGTH" ) ).value<QgsProperty>();
const bool fixedDist = parameterAsBool( parameters, QStringLiteral( "FIXED_DISTANCE" ), context );
double interval = 0.0;
if ( fixedDist )
interval = parameterAsDouble( parameters, QStringLiteral( "INTERVAL" ), context );
if ( orientation == QgsTransectAlgorithm::Both ) if ( orientation == QgsTransectAlgorithm::Both )
length /= 2.0; length /= 2.0;
@ -145,7 +155,6 @@ QVariantMap QgsTransectAlgorithm::processAlgorithm( const QVariantMap &parameter
const double step = source->featureCount() > 0 ? 100.0 / source->featureCount() : 1; const double step = source->featureCount() > 0 ? 100.0 / source->featureCount() : 1;
QgsFeature feat; QgsFeature feat;
while ( features.nextFeature( feat ) ) while ( features.nextFeature( feat ) )
{ {
current++; current++;
@ -174,24 +183,60 @@ QVariantMap QgsTransectAlgorithm::processAlgorithm( const QVariantMap &parameter
inputGeometry.convertToMultiType(); inputGeometry.convertToMultiType();
const QgsMultiLineString *multiLine = static_cast<const QgsMultiLineString *>( inputGeometry.constGet() ); const QgsMultiLineString *multiLine = static_cast<const QgsMultiLineString *>( inputGeometry.constGet() );
for ( int id = 0; id < multiLine->numGeometries(); ++id )
for ( int part = 0; part < multiLine->numGeometries(); ++part )
{ {
const QgsLineString *line = multiLine->lineStringN( id ); const QgsLineString *lineString = multiLine->lineStringN( part );
QgsAbstractGeometry::vertex_iterator it = line->vertices_begin(); if ( !lineString )
while ( it != line->vertices_end() ) continue;
QgsLineString line = *lineString;
std::vector<QgsPoint> samplingPoints;
// Determine sampling points based on mode (fixed distance or vertices)
if ( fixedDist )
{ {
const QgsVertexId vertexId = it.vertexId(); double totalLength = line.length();
const int i = vertexId.vertex; for ( double d = 0; d <= totalLength; d += interval )
{
QgsPoint *pt = line.interpolatePoint( d );
samplingPoints.push_back( *pt );
}
}
else
{
for ( auto it = line.vertices_begin(); it != line.vertices_end(); ++it )
samplingPoints.push_back( *it );
}
for ( int i = 0; i < static_cast<int>( samplingPoints.size() ); ++i )
{
const QgsPoint &pt = samplingPoints[i];
double azimuth = 0;
if ( fixedDist )
{
QgsPoint segPt;
QgsVertexId vid;
line.closestSegment( pt, segPt, vid, nullptr, Qgis::DEFAULT_SEGMENT_EPSILON );
QgsVertexId prev( vid.part, vid.ring, vid.vertex - 1 );
azimuth = line.vertexAt( prev ).azimuth( line.vertexAt( vid ) ) * M_PI / 180.0;
}
else
{
azimuth = line.vertexAngle( QgsVertexId( part, 0, i ) );
}
QgsFeature outFeat; QgsFeature outFeat;
QgsAttributes attrs = feat.attributes(); QgsAttributes attrs = feat.attributes();
attrs << current << number << i + 1 << evaluatedAngle << ( ( orientation == QgsTransectAlgorithm::Both ) ? evaluatedLength * 2 : evaluatedLength ) << orientation; attrs << current << number << i + 1 << evaluatedAngle
<< ( ( orientation == QgsTransectAlgorithm::Both ) ? evaluatedLength * 2 : evaluatedLength )
<< static_cast<int>( orientation );
outFeat.setAttributes( attrs ); outFeat.setAttributes( attrs );
const double angleAtVertex = line->vertexAngle( vertexId ); outFeat.setGeometry( calcTransect( pt, azimuth, evaluatedLength, orientation, evaluatedAngle ) );
outFeat.setGeometry( calcTransect( *it, angleAtVertex, evaluatedLength, orientation, evaluatedAngle ) );
if ( !sink->addFeature( outFeat, QgsFeatureSink::FastInsert ) ) if ( !sink->addFeature( outFeat, QgsFeatureSink::FastInsert ) )
throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) ); throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
number++; number++;
it++;
} }
} }
} }
@ -203,7 +248,6 @@ QVariantMap QgsTransectAlgorithm::processAlgorithm( const QVariantMap &parameter
return outputs; return outputs;
} }
QgsGeometry QgsTransectAlgorithm::calcTransect( const QgsPoint &point, const double angleAtVertex, const double length, const QgsTransectAlgorithm::Side orientation, const double angle ) QgsGeometry QgsTransectAlgorithm::calcTransect( const QgsPoint &point, const double angleAtVertex, const double length, const QgsTransectAlgorithm::Side orientation, const double angle )
{ {
QgsPoint pLeft; // left point of the line QgsPoint pLeft; // left point of the line

View File

@ -5897,6 +5897,10 @@ class CORE_EXPORT Qgis
ResizeRightUp, //!< Resize right up (Top right handle) ResizeRightUp, //!< Resize right up (Top right handle)
ResizeLeftDown, //!< Resize left down (Bottom left handle) ResizeLeftDown, //!< Resize left down (Bottom left handle)
ResizeRightDown, //!< Resize right down (Bottom right handle) ResizeRightDown, //!< Resize right down (Bottom right handle)
RotateTopLeft, //!< Rotate from top left handle. \since QGIS 4.0
RotateTopRight, //!< Rotate from top right handle. \since QGIS 4.0
RotateBottomLeft, //!< Rotate from bottom left handle. \since QGIS 4.0
RotateBottomRight, //!< Rotate right bottom right handle. \since QGIS 4.0
SelectItem, //!< Select item SelectItem, //!< Select item
NoAction //!< No action NoAction //!< No action
}; };

View File

@ -196,6 +196,10 @@ void QgsLayoutGuiUtils::registerGuiForKnownItemTypes( QgsMapCanvas *mapCanvas )
case Qgis::MouseHandlesAction::MoveItem: case Qgis::MouseHandlesAction::MoveItem:
case Qgis::MouseHandlesAction::NoAction: case Qgis::MouseHandlesAction::NoAction:
case Qgis::MouseHandlesAction::SelectItem: case Qgis::MouseHandlesAction::SelectItem:
case Qgis::MouseHandlesAction::RotateTopLeft:
case Qgis::MouseHandlesAction::RotateTopRight:
case Qgis::MouseHandlesAction::RotateBottomLeft:
case Qgis::MouseHandlesAction::RotateBottomRight:
return; return;
case Qgis::MouseHandlesAction::ResizeUp: case Qgis::MouseHandlesAction::ResizeUp:

View File

@ -44,6 +44,8 @@ QgsLayoutMouseHandles::QgsLayoutMouseHandles( QgsLayout *layout, QgsLayoutView *
, mLayout( layout ) , mLayout( layout )
, mView( view ) , mView( view )
{ {
setRotationEnabled( true );
//listen for selection changes, and update handles accordingly //listen for selection changes, and update handles accordingly
connect( mLayout, &QGraphicsScene::selectionChanged, this, &QgsLayoutMouseHandles::selectionChanged ); connect( mLayout, &QGraphicsScene::selectionChanged, this, &QgsLayoutMouseHandles::selectionChanged );
@ -251,6 +253,16 @@ void QgsLayoutMouseHandles::moveItem( QGraphicsItem *item, double deltaX, double
qgis::down_cast<QgsLayoutItem *>( item )->attemptMoveBy( deltaX, deltaY ); qgis::down_cast<QgsLayoutItem *>( item )->attemptMoveBy( deltaX, deltaY );
} }
void QgsLayoutMouseHandles::rotateItem( QGraphicsItem *item, double deltaDegree, double deltaCenterX, double deltaCenterY )
{
QgsLayoutItem *itm = qgis::down_cast<QgsLayoutItem *>( item );
QgsLayoutItem::ReferencePoint previousReferencePoint = itm->referencePoint();
itm->setReferencePoint( QgsLayoutItem::Middle );
itm->attemptMoveBy( deltaCenterX, deltaCenterY );
itm->setItemRotation( itm->itemRotation() + deltaDegree, true );
itm->setReferencePoint( previousReferencePoint );
}
void QgsLayoutMouseHandles::setItemRect( QGraphicsItem *item, QRectF rect ) void QgsLayoutMouseHandles::setItemRect( QGraphicsItem *item, QRectF rect )
{ {
QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item ); QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item );

View File

@ -74,6 +74,7 @@ class GUI_EXPORT QgsLayoutMouseHandles : public QgsGraphicsViewMouseHandles
void expandItemList( const QList<QGraphicsItem *> &items, QList<QGraphicsItem *> &collected ) const override; void expandItemList( const QList<QGraphicsItem *> &items, QList<QGraphicsItem *> &collected ) const override;
void expandItemList( const QList<QgsLayoutItem *> &items, QList<QGraphicsItem *> &collected ) const; void expandItemList( const QList<QgsLayoutItem *> &items, QList<QGraphicsItem *> &collected ) const;
void moveItem( QGraphicsItem *item, double deltaX, double deltaY ) override; void moveItem( QGraphicsItem *item, double deltaX, double deltaY ) override;
void rotateItem( QGraphicsItem *item, double deltaDegree, double deltaCenterX, double deltaCenterY ) override;
void setItemRect( QGraphicsItem *item, QRectF rect ) override; void setItemRect( QGraphicsItem *item, QRectF rect ) override;
void showStatusMessage( const QString &message ) override; void showStatusMessage( const QString &message ) override;
void hideAlignItems() override; void hideAlignItems() override;

View File

@ -15,10 +15,13 @@
* * * *
***************************************************************************/ ***************************************************************************/
#include "qgsgraphicsviewmousehandles.h"
#include "moc_qgsgraphicsviewmousehandles.cpp" #include "moc_qgsgraphicsviewmousehandles.cpp"
#include "qgsrendercontext.h"
#include "qgis.h" #include "qgis.h"
#include "qgsgraphicsviewmousehandles.h"
#include "qgslayoututils.h"
#include "qgsrendercontext.h"
#include <QGraphicsView> #include <QGraphicsView>
#include <QGraphicsSceneHoverEvent> #include <QGraphicsSceneHoverEvent>
#include <QPainter> #include <QPainter>
@ -34,6 +37,28 @@ QgsGraphicsViewMouseHandles::QgsGraphicsViewMouseHandles( QGraphicsView *view )
{ {
//accept hover events, required for changing cursor to resize cursors //accept hover events, required for changing cursor to resize cursors
setAcceptHoverEvents( true ); 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 * ) void QgsGraphicsViewMouseHandles::paintInternal( QPainter *painter, bool showHandles, bool showStaticBoundingBoxes, bool showTemporaryBoundingBoxes, const QStyleOptionGraphicsItem *, QWidget * )
@ -62,6 +87,11 @@ QRectF QgsGraphicsViewMouseHandles::storedItemRect( QGraphicsItem *item ) const
return itemRect( item ); return itemRect( item );
} }
void QgsGraphicsViewMouseHandles::rotateItem( QGraphicsItem *, double, double, double )
{
QgsDebugError( QStringLiteral( "Rotation is not implemented for this class" ) );
}
void QgsGraphicsViewMouseHandles::previewItemMove( QGraphicsItem *, double, double ) void QgsGraphicsViewMouseHandles::previewItemMove( QGraphicsItem *, double, double )
{ {
} }
@ -108,7 +138,7 @@ void QgsGraphicsViewMouseHandles::drawHandles( QPainter *painter, double rectHan
painter->setBrush( Qt::NoBrush ); painter->setBrush( Qt::NoBrush );
painter->drawRect( QRectF( 0, 0, rect().width(), rect().height() ) ); painter->drawRect( QRectF( 0, 0, rect().width(), rect().height() ) );
//draw resize handles, using a filled white box //draw resize handles, using filled white boxes
painter->setBrush( QColor( 255, 255, 255, 255 ) ); painter->setBrush( QColor( 255, 255, 255, 255 ) );
//top left //top left
painter->drawRect( QRectF( 0, 0, rectHandlerSize, rectHandlerSize ) ); painter->drawRect( QRectF( 0, 0, rectHandlerSize, rectHandlerSize ) );
@ -126,6 +156,63 @@ void QgsGraphicsViewMouseHandles::drawHandles( QPainter *painter, double rectHan
painter->drawRect( QRectF( ( rect().width() - rectHandlerSize ) / 2, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) ); painter->drawRect( QRectF( ( rect().width() - rectHandlerSize ) / 2, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) );
//bottom right //bottom right
painter->drawRect( QRectF( rect().width() - rectHandlerSize, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) ); 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 ) void QgsGraphicsViewMouseHandles::drawSelectedItemBounds( QPainter *painter )
@ -181,6 +268,17 @@ void QgsGraphicsViewMouseHandles::drawSelectedItemBounds( QPainter *painter )
relativeResizeRect( thisItemRect, QRectF( -mResizeMoveX, -mResizeMoveY, mBeginHandleWidth, mBeginHandleHeight ), mResizeRect ); relativeResizeRect( thisItemRect, QRectF( -mResizeMoveX, -mResizeMoveY, mBeginHandleWidth, mBeginHandleHeight ), mResizeRect );
itemBounds = QPolygonF( thisItemRect ); 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 else
{ {
// not resizing or moving, so just map the item's bounds to the mouse handle item's coordinate system // not resizing or moving, so just map the item's bounds to the mouse handle item's coordinate system
@ -195,7 +293,7 @@ void QgsGraphicsViewMouseHandles::drawSelectedItemBounds( QPainter *painter )
} }
} }
double QgsGraphicsViewMouseHandles::rectHandlerBorderTolerance() double QgsGraphicsViewMouseHandles::rectHandlerBorderTolerance() const
{ {
if ( !mView ) if ( !mView )
return 0; return 0;
@ -312,6 +410,12 @@ Qt::CursorShape QgsGraphicsViewMouseHandles::cursorForPosition( QPointF itemCoor
} }
case Qgis::MouseHandlesAction::SelectItem: case Qgis::MouseHandlesAction::SelectItem:
return Qt::ArrowCursor; return Qt::ArrowCursor;
case Qgis::MouseHandlesAction::RotateTopLeft:
case Qgis::MouseHandlesAction::RotateTopRight:
case Qgis::MouseHandlesAction::RotateBottomLeft:
case Qgis::MouseHandlesAction::RotateBottomRight:
return Qt::PointingHandCursor;
} }
return Qt::ArrowCursor; return Qt::ArrowCursor;
@ -324,8 +428,14 @@ Qgis::MouseHandlesAction QgsGraphicsViewMouseHandles::mouseActionForPosition( QP
bool nearLowerBorder = false; bool nearLowerBorder = false;
bool nearUpperBorder = false; bool nearUpperBorder = false;
bool nearLeftInner = false;
bool nearRightInner = false;
bool nearLowerInner = false;
bool nearUpperInner = false;
bool withinWidth = false; bool withinWidth = false;
bool withinHeight = false; bool withinHeight = false;
if ( itemCoordPos.x() >= 0 && itemCoordPos.x() <= rect().width() ) if ( itemCoordPos.x() >= 0 && itemCoordPos.x() <= rect().width() )
{ {
withinWidth = true; withinWidth = true;
@ -336,23 +446,40 @@ Qgis::MouseHandlesAction QgsGraphicsViewMouseHandles::mouseActionForPosition( QP
} }
double borderTolerance = rectHandlerBorderTolerance(); double borderTolerance = rectHandlerBorderTolerance();
double innerTolerance = mRotationHandleSize * borderTolerance / mHandleSize;
if ( itemCoordPos.x() >= 0 && itemCoordPos.x() < borderTolerance ) if ( itemCoordPos.x() >= 0 && itemCoordPos.x() < borderTolerance )
{ {
nearLeftBorder = true; nearLeftBorder = true;
} }
else if ( isRotationEnabled() && itemCoordPos.x() >= borderTolerance && itemCoordPos.x() < ( borderTolerance + innerTolerance ) )
{
nearLeftInner = true;
}
if ( itemCoordPos.y() >= 0 && itemCoordPos.y() < borderTolerance ) if ( itemCoordPos.y() >= 0 && itemCoordPos.y() < borderTolerance )
{ {
nearUpperBorder = true; nearUpperBorder = true;
} }
else if ( isRotationEnabled() && itemCoordPos.y() >= borderTolerance && itemCoordPos.y() < ( borderTolerance + innerTolerance ) )
{
nearUpperInner = true;
}
if ( itemCoordPos.x() <= rect().width() && itemCoordPos.x() > ( rect().width() - borderTolerance ) ) if ( itemCoordPos.x() <= rect().width() && itemCoordPos.x() > ( rect().width() - borderTolerance ) )
{ {
nearRightBorder = true; 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 ) ) if ( itemCoordPos.y() <= rect().height() && itemCoordPos.y() > ( rect().height() - borderTolerance ) )
{ {
nearLowerBorder = true; nearLowerBorder = true;
} }
else if ( isRotationEnabled() && itemCoordPos.y() <= ( rect().height() - borderTolerance ) && itemCoordPos.y() > ( rect().height() - borderTolerance - innerTolerance ) )
{
nearLowerInner = true;
}
if ( nearLeftBorder && nearUpperBorder ) if ( nearLeftBorder && nearUpperBorder )
{ {
@ -386,6 +513,22 @@ Qgis::MouseHandlesAction QgsGraphicsViewMouseHandles::mouseActionForPosition( QP
{ {
return Qgis::MouseHandlesAction::ResizeDown; return Qgis::MouseHandlesAction::ResizeDown;
} }
else if ( nearLeftInner && nearUpperInner )
{
return Qgis::MouseHandlesAction::RotateTopLeft;
}
else if ( nearRightInner && nearUpperInner )
{
return Qgis::MouseHandlesAction::RotateTopRight;
}
else if ( nearLeftInner && nearLowerInner )
{
return Qgis::MouseHandlesAction::RotateBottomLeft;
}
else if ( nearRightInner && nearLowerInner )
{
return Qgis::MouseHandlesAction::RotateBottomRight;
}
//find out if cursor position is over a selected item //find out if cursor position is over a selected item
QPointF scenePoint = mapToScene( itemCoordPos ); QPointF scenePoint = mapToScene( itemCoordPos );
@ -489,19 +632,42 @@ void QgsGraphicsViewMouseHandles::mousePressEvent( QGraphicsSceneMouseEvent *eve
hideAlignItems(); hideAlignItems();
if ( mCurrentMouseMoveAction == Qgis::MouseHandlesAction::MoveItem ) switch ( mCurrentMouseMoveAction )
{ {
//moving items case Qgis::MouseHandlesAction::MoveItem:
mIsDragging = true; //moving items
} mIsDragging = true;
else if ( mCurrentMouseMoveAction != Qgis::MouseHandlesAction::SelectItem && mCurrentMouseMoveAction != Qgis::MouseHandlesAction::NoAction ) break;
{
//resizing items case Qgis::MouseHandlesAction::ResizeUp:
mIsResizing = true; case Qgis::MouseHandlesAction::ResizeDown:
mResizeRect = QRectF( 0, 0, mBeginHandleWidth, mBeginHandleHeight ); case Qgis::MouseHandlesAction::ResizeLeft:
mResizeMoveX = 0; case Qgis::MouseHandlesAction::ResizeRight:
mResizeMoveY = 0; case Qgis::MouseHandlesAction::ResizeLeftUp:
mCursorOffset = calcCursorEdgeOffset( mMouseMoveStartPos ); 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::RotateTopLeft:
case Qgis::MouseHandlesAction::RotateTopRight:
case Qgis::MouseHandlesAction::RotateBottomLeft:
case Qgis::MouseHandlesAction::RotateBottomRight:
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;
} }
} }
@ -537,6 +703,12 @@ void QgsGraphicsViewMouseHandles::mouseMoveEvent( QGraphicsSceneMouseEvent *even
//resize from center if alt depressed //resize from center if alt depressed
resizeMouseMove( event->lastScenePos(), event->modifiers() & Qt::ShiftModifier, event->modifiers() & Qt::AltModifier ); 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 ) void QgsGraphicsViewMouseHandles::mouseReleaseEvent( QGraphicsSceneMouseEvent *event )
@ -567,12 +739,13 @@ void QgsGraphicsViewMouseHandles::mouseReleaseEvent( QGraphicsSceneMouseEvent *e
{ {
mIsDragging = false; mIsDragging = false;
mIsResizing = false; mIsResizing = false;
mIsRotating = false;
update(); update();
hideAlignItems(); hideAlignItems();
return; return;
} }
if ( mCurrentMouseMoveAction == Qgis::MouseHandlesAction::MoveItem ) if ( mIsDragging )
{ {
//move selected items //move selected items
startMacroCommand( tr( "Move Items" ) ); startMacroCommand( tr( "Move Items" ) );
@ -597,8 +770,10 @@ void QgsGraphicsViewMouseHandles::mouseReleaseEvent( QGraphicsSceneMouseEvent *e
endItemCommand( item ); endItemCommand( item );
} }
endMacroCommand(); endMacroCommand();
mIsDragging = false;
} }
else if ( mCurrentMouseMoveAction != Qgis::MouseHandlesAction::NoAction ) else if ( mIsResizing )
{ {
//resize selected items //resize selected items
startMacroCommand( tr( "Resize Items" ) ); startMacroCommand( tr( "Resize Items" ) );
@ -635,17 +810,44 @@ void QgsGraphicsViewMouseHandles::mouseReleaseEvent( QGraphicsSceneMouseEvent *e
endItemCommand( item ); endItemCommand( item );
} }
endMacroCommand(); 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(); hideAlignItems();
if ( mIsDragging )
{
mIsDragging = false;
}
if ( mIsResizing )
{
mIsResizing = false;
}
//reset default action //reset default action
mCurrentMouseMoveAction = Qgis::MouseHandlesAction::MoveItem; mCurrentMouseMoveAction = Qgis::MouseHandlesAction::MoveItem;
@ -721,6 +923,36 @@ void QgsGraphicsViewMouseHandles::updateHandles()
update(); 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 )
{
mRotationDelta = QgsLayoutUtils::snappedAngle( mRotationDelta );
}
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 ) void QgsGraphicsViewMouseHandles::dragMouseMove( QPointF currentPosition, bool lockMovement, bool preventSnap )
{ {
if ( !scene() ) if ( !scene() )
@ -985,6 +1217,10 @@ void QgsGraphicsViewMouseHandles::resizeMouseMove( QPointF currentPosition, bool
break; break;
} }
case Qgis::MouseHandlesAction::RotateTopLeft:
case Qgis::MouseHandlesAction::RotateTopRight:
case Qgis::MouseHandlesAction::RotateBottomLeft:
case Qgis::MouseHandlesAction::RotateBottomRight:
case Qgis::MouseHandlesAction::MoveItem: case Qgis::MouseHandlesAction::MoveItem:
case Qgis::MouseHandlesAction::SelectItem: case Qgis::MouseHandlesAction::SelectItem:
case Qgis::MouseHandlesAction::NoAction: case Qgis::MouseHandlesAction::NoAction:
@ -1099,6 +1335,10 @@ QSizeF QgsGraphicsViewMouseHandles::calcCursorEdgeOffset( QPointF cursorPos )
case Qgis::MouseHandlesAction::ResizeLeftDown: case Qgis::MouseHandlesAction::ResizeLeftDown:
return QSizeF( sceneMousePos.x(), sceneMousePos.y() - rect().height() ); return QSizeF( sceneMousePos.x(), sceneMousePos.y() - rect().height() );
case Qgis::MouseHandlesAction::RotateTopLeft:
case Qgis::MouseHandlesAction::RotateTopRight:
case Qgis::MouseHandlesAction::RotateBottomLeft:
case Qgis::MouseHandlesAction::RotateBottomRight:
case Qgis::MouseHandlesAction::MoveItem: case Qgis::MouseHandlesAction::MoveItem:
case Qgis::MouseHandlesAction::SelectItem: case Qgis::MouseHandlesAction::SelectItem:
case Qgis::MouseHandlesAction::NoAction: case Qgis::MouseHandlesAction::NoAction:

View File

@ -77,11 +77,37 @@ class GUI_EXPORT QgsGraphicsViewMouseHandles : public QObject, public QGraphicsR
//! Returns TRUE is user is currently resizing with the handles //! Returns TRUE is user is currently resizing with the handles
bool isResizing() const { return mIsResizing; } bool isResizing() const { return mIsResizing; }
/**
* Returns TRUE is user is currently rotating with the handles.
*
* \since QGIS 4.0
*/
bool isRotating() const { return mIsRotating; }
bool shouldBlockEvent( QInputEvent *event ) const; bool shouldBlockEvent( QInputEvent *event ) const;
//! Initializes a drag operation \since QGIS 3.34 //! Initializes a drag operation \since QGIS 3.34
void startMove( QPointF sceneCoordPos ); void startMove( QPointF sceneCoordPos );
/**
* Returns TRUE if rotation functionality is enabled.
*
* Rotation is not enabled by default.
*
* \since QGIS 4.0
*/
bool isRotationEnabled() const { return mRotationEnabled; }
/**
* Sets whether rotation functionality is enabled.
*
* Rotation is not enabled by default. Subclasses must implement the
* rotateItem() method in order to support rotation.
*
* \since QGIS 4.0
*/
void setRotationEnabled( bool enable );
public slots: public slots:
//! Redraws handles when selected item size changes //! Redraws handles when selected item size changes
@ -111,6 +137,7 @@ class GUI_EXPORT QgsGraphicsViewMouseHandles : public QObject, public QGraphicsR
virtual QRectF itemRect( QGraphicsItem *item ) const = 0; virtual QRectF itemRect( QGraphicsItem *item ) const = 0;
virtual QRectF storedItemRect( QGraphicsItem *item ) const; virtual QRectF storedItemRect( QGraphicsItem *item ) const;
virtual void moveItem( QGraphicsItem *item, double deltaX, double deltaY ) = 0; virtual void moveItem( QGraphicsItem *item, double deltaX, double deltaY ) = 0;
virtual void rotateItem( QGraphicsItem *item, double deltaDegree, double deltaCenterX, double deltaCenterY );
virtual void previewItemMove( QGraphicsItem *item, double deltaX, double deltaY ); virtual void previewItemMove( QGraphicsItem *item, double deltaX, double deltaY );
virtual void setItemRect( QGraphicsItem *item, QRectF rect ) = 0; virtual void setItemRect( QGraphicsItem *item, QRectF rect ) = 0;
@ -158,6 +185,9 @@ class GUI_EXPORT QgsGraphicsViewMouseHandles : public QObject, public QGraphicsR
//! Handles resizing of items during mouse move //! Handles resizing of items during mouse move
void resizeMouseMove( QPointF currentPosition, bool lockAspect, bool fromCenter ); void resizeMouseMove( QPointF currentPosition, bool lockAspect, bool fromCenter );
//! Handles rotating of tiems during mouse move
void rotateMouseMove( QPointF currentPosition, bool snapToCommonAngles );
void setHandleSize( double size ); void setHandleSize( double size );
//! Finds out which mouse move action to choose depending on the cursor position inside the widget //! Finds out which mouse move action to choose depending on the cursor position inside the widget
@ -194,6 +224,8 @@ class GUI_EXPORT QgsGraphicsViewMouseHandles : public QObject, public QGraphicsR
QGraphicsView *mView = nullptr; QGraphicsView *mView = nullptr;
double mHandleSize = 10; double mHandleSize = 10;
double mRotationHandleSize = 20;
QPainterPath mRotationHandlePath;
QSizeF mCursorOffset; QSizeF mCursorOffset;
double mResizeMoveX = 0; double mResizeMoveX = 0;
@ -205,6 +237,16 @@ class GUI_EXPORT QgsGraphicsViewMouseHandles : public QObject, public QGraphicsR
QRectF mResizeRect; QRectF mResizeRect;
bool mRotationEnabled = false;
//! Center point around which rotation occurs
QPointF mRotationCenter;
//! The starting rotation angle from center point
double mRotationBegin = 0.0;
//! The current rotation angle from center point
double mRotationCurrent = 0.0;
//! The rotation angle delta to be applied (can be snapped to common angle)
double mRotationDelta = 0.0;
//! Start point of the last mouse move action (in scene coordinates) //! Start point of the last mouse move action (in scene coordinates)
QPointF mMouseMoveStartPos; QPointF mMouseMoveStartPos;
@ -215,6 +257,8 @@ class GUI_EXPORT QgsGraphicsViewMouseHandles : public QObject, public QGraphicsR
bool mIsDragging = false; bool mIsDragging = false;
//! True is user is currently resizing items //! True is user is currently resizing items
bool mIsResizing = false; bool mIsResizing = false;
//! True is user is currently rotating items
bool mIsRotating = false;
//! Position of the mouse at beginning of move/resize (in scene coordinates) //! Position of the mouse at beginning of move/resize (in scene coordinates)
QPointF mBeginMouseEventPos; QPointF mBeginMouseEventPos;
@ -232,7 +276,7 @@ class GUI_EXPORT QgsGraphicsViewMouseHandles : public QObject, public QGraphicsR
* Returns the current (zoom level dependent) tolerance to decide if mouse position is close enough to the * Returns the current (zoom level dependent) tolerance to decide if mouse position is close enough to the
* item border for resizing. * item border for resizing.
*/ */
double rectHandlerBorderTolerance(); double rectHandlerBorderTolerance() const;
//! Finds out the appropriate cursor for the current mouse position in the widget (e.g. move in the middle, resize at border) //! Finds out the appropriate cursor for the current mouse position in the widget (e.g. move in the middle, resize at border)
Qt::CursorShape cursorForPosition( QPointF itemCoordPos ); Qt::CursorShape cursorForPosition( QPointF itemCoordPos );