diff --git a/python/core/layout/qgslayoutsnapper.sip b/python/core/layout/qgslayoutsnapper.sip index 09d7af0ada7..0a7a6a1700b 100644 --- a/python/core/layout/qgslayoutsnapper.sip +++ b/python/core/layout/qgslayoutsnapper.sip @@ -83,7 +83,8 @@ class QgsLayoutSnapper: QgsLayoutSerializableObject %End QPointF snapPoint( QPointF point, double scaleFactor, bool &snapped /Out/, QGraphicsLineItem *horizontalSnapLine = 0, - QGraphicsLineItem *verticalSnapLine = 0 ) const; + QGraphicsLineItem *verticalSnapLine = 0, + const QList< QgsLayoutItem * > *ignoreItems = 0 ) const; %Docstring Snaps a layout coordinate ``point``. If ``point`` was snapped, ``snapped`` will be set to true. @@ -95,6 +96,8 @@ class QgsLayoutSnapper: QgsLayoutSerializableObject If the ``horizontalSnapLine`` and ``verticalSnapLine`` arguments are specified, then the snapper will automatically display and position these lines to indicate snapping positions to item bounds. + + A list of items to ignore during the snapping can be specified via the ``ignoreItems`` list. :rtype: QPointF %End diff --git a/src/core/layout/qgslayoutsnapper.cpp b/src/core/layout/qgslayoutsnapper.cpp index 2ce7f3879f4..37d5061bd26 100644 --- a/src/core/layout/qgslayoutsnapper.cpp +++ b/src/core/layout/qgslayoutsnapper.cpp @@ -49,7 +49,8 @@ void QgsLayoutSnapper::setSnapToItems( bool enabled ) mSnapToItems = enabled; } -QPointF QgsLayoutSnapper::snapPoint( QPointF point, double scaleFactor, bool &snapped, QGraphicsLineItem *horizontalSnapLine, QGraphicsLineItem *verticalSnapLine ) const +QPointF QgsLayoutSnapper::snapPoint( QPointF point, double scaleFactor, bool &snapped, QGraphicsLineItem *horizontalSnapLine, QGraphicsLineItem *verticalSnapLine, + const QList< QgsLayoutItem * > *ignoreItems ) const { snapped = false; @@ -77,7 +78,7 @@ QPointF QgsLayoutSnapper::snapPoint( QPointF point, double scaleFactor, bool &sn bool snappedYToItems = false; if ( !snappedXToGuides ) { - newX = snapPointToItems( point.x(), Qt::Horizontal, scaleFactor, QList< QgsLayoutItem * >(), snappedXToItems, verticalSnapLine ); + newX = snapPointToItems( point.x(), Qt::Horizontal, scaleFactor, ignoreItems ? *ignoreItems : QList< QgsLayoutItem * >(), snappedXToItems, verticalSnapLine ); if ( snappedXToItems ) { snapped = true; @@ -86,7 +87,7 @@ QPointF QgsLayoutSnapper::snapPoint( QPointF point, double scaleFactor, bool &sn } if ( !snappedYToGuides ) { - newY = snapPointToItems( point.y(), Qt::Vertical, scaleFactor, QList< QgsLayoutItem * >(), snappedYToItems, horizontalSnapLine ); + newY = snapPointToItems( point.y(), Qt::Vertical, scaleFactor, ignoreItems ? *ignoreItems : QList< QgsLayoutItem * >(), snappedYToItems, horizontalSnapLine ); if ( snappedYToItems ) { snapped = true; diff --git a/src/core/layout/qgslayoutsnapper.h b/src/core/layout/qgslayoutsnapper.h index f4be5b985a3..7affebd6d08 100644 --- a/src/core/layout/qgslayoutsnapper.h +++ b/src/core/layout/qgslayoutsnapper.h @@ -105,9 +105,12 @@ class CORE_EXPORT QgsLayoutSnapper: public QgsLayoutSerializableObject * * If the \a horizontalSnapLine and \a verticalSnapLine arguments are specified, then the snapper * will automatically display and position these lines to indicate snapping positions to item bounds. + * + * A list of items to ignore during the snapping can be specified via the \a ignoreItems list. */ QPointF snapPoint( QPointF point, double scaleFactor, bool &snapped SIP_OUT, QGraphicsLineItem *horizontalSnapLine = nullptr, - QGraphicsLineItem *verticalSnapLine = nullptr ) const; + QGraphicsLineItem *verticalSnapLine = nullptr, + const QList< QgsLayoutItem * > *ignoreItems = nullptr ) const; /** * Snaps a layout coordinate \a point to the grid. If \a point diff --git a/src/gui/layout/qgslayoutmousehandles.cpp b/src/gui/layout/qgslayoutmousehandles.cpp index 19711cc4cd2..0d8256d500b 100644 --- a/src/gui/layout/qgslayoutmousehandles.cpp +++ b/src/gui/layout/qgslayoutmousehandles.cpp @@ -24,6 +24,7 @@ #include "qgslayoututils.h" #include "qgslayoutview.h" #include "qgslayoutviewtoolselect.h" +#include "qgslayoutsnapper.h" #include #include #include @@ -41,6 +42,13 @@ QgsLayoutMouseHandles::QgsLayoutMouseHandles( QgsLayout *layout, QgsLayoutView * //accept hover events, required for changing cursor to resize cursors setAcceptHoverEvents( true ); + + mHorizontalSnapLine.reset( mView->createSnapLine() ); + mHorizontalSnapLine->hide(); + layout->addItem( mHorizontalSnapLine.get() ); + mVerticalSnapLine.reset( mView->createSnapLine() ); + mVerticalSnapLine->hide(); + layout->addItem( mVerticalSnapLine.get() ); } void QgsLayoutMouseHandles::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget ) @@ -697,6 +705,58 @@ void QgsLayoutMouseHandles::resetStatusBar() } } +QPointF QgsLayoutMouseHandles::snapPoint( QPointF originalPoint, QgsLayoutMouseHandles::SnapGuideMode mode ) +{ + //align item + double alignX = 0; + double alignY = 0; + + bool snapped = false; + + const QList< QgsLayoutItem * > selectedItems = mLayout->selectedLayoutItems(); + + //depending on the mode, we either snap just the single point, or all the bounds of the selection + QPointF snappedPoint; + switch ( mode ) + { + case Item: + //snappedPoint = alignItem( alignX, alignY, point.x(), point.y() ); + break; + case Point: + snappedPoint = mLayout->snapper().snapPoint( originalPoint, mView->transform().m11(), snapped, mHorizontalSnapLine.get(), mVerticalSnapLine.get(), &selectedItems ); + break; + } +#if 0 + if ( !qgsDoubleNear( alignX, -1.0 ) ) + { + QGraphicsLineItem *item = hAlignSnapItem(); + int numPages = mComposition->numPages(); + double yLineCoord = 300; //default in case there is no single page + if ( numPages > 0 ) + { + yLineCoord = mComposition->paperHeight() * numPages + mComposition->spaceBetweenPages() * ( numPages - 1 ); + } + item->setLine( QLineF( alignX, 0, alignX, yLineCoord ) ); + item->show(); + } + else + { + deleteHAlignSnapItem(); + } + if ( !qgsDoubleNear( alignY, -1.0 ) ) + { + QGraphicsLineItem *item = vAlignSnapItem(); + item->setLine( QLineF( 0, alignY, mComposition->paperWidth(), alignY ) ); + item->show(); + } + else + { + deleteVAlignSnapItem(); + } +#endif + return snappedPoint; +} + void QgsLayoutMouseHandles::mousePressEvent( QGraphicsSceneMouseEvent *event ) { //save current cursor position @@ -797,9 +857,6 @@ void QgsLayoutMouseHandles::dragMouseMove( QPointF currentPosition, bool lockMov QPointF snappedLeftPoint; - //TODO - snappedLeftPoint = upperLeftPoint; -#if 0 //no snapping for rotated items for now if ( !preventSnap && qgsDoubleNear( rotation(), 0.0 ) ) { @@ -810,9 +867,11 @@ void QgsLayoutMouseHandles::dragMouseMove( QPointF currentPosition, bool lockMov { //no snapping snappedLeftPoint = upperLeftPoint; +#if 0 deleteAlignItems(); - } #endif + } + //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(); @@ -859,11 +918,8 @@ void QgsLayoutMouseHandles::resizeMouseMove( QPointF currentPosition, bool lockR //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() ) ); -#if 0 QPointF snappedPosition = snapPoint( QPointF( currentPosition.x() - mCursorOffset.width(), currentPosition.y() - mCursorOffset.height() ), QgsLayoutMouseHandles::Point ); finalPosition = mapFromScene( snappedPosition ); -#endif - finalPosition = mapFromScene( currentPosition ); } else { diff --git a/src/gui/layout/qgslayoutmousehandles.h b/src/gui/layout/qgslayoutmousehandles.h index 49832789bac..52ae478c96f 100644 --- a/src/gui/layout/qgslayoutmousehandles.h +++ b/src/gui/layout/qgslayoutmousehandles.h @@ -22,6 +22,7 @@ #include #include #include +#include #include "qgis_gui.h" @@ -157,8 +158,8 @@ class GUI_EXPORT QgsLayoutMouseHandles: public QObject, public QGraphicsRectItem bool mIsResizing = false; //! Align snap lines - QGraphicsLineItem *mHAlignSnapItem = nullptr; - QGraphicsLineItem *mVAlignSnapItem = nullptr; + std::unique_ptr< QGraphicsLineItem > mHorizontalSnapLine; + std::unique_ptr< QGraphicsLineItem > mVerticalSnapLine; QSizeF mCursorOffset; @@ -199,6 +200,10 @@ class GUI_EXPORT QgsLayoutMouseHandles: public QObject, public QGraphicsRectItem //resets the composer window status bar to the default message void resetStatusBar(); + + //! Snaps an item or point (depending on mode) originating at originalPoint to the grid or align rulers + QPointF snapPoint( QPointF originalPoint, SnapGuideMode mode ); + }; ///@endcond PRIVATE diff --git a/src/gui/layout/qgslayoutview.h b/src/gui/layout/qgslayoutview.h index 1e19d354caf..bf90abfd39c 100644 --- a/src/gui/layout/qgslayoutview.h +++ b/src/gui/layout/qgslayoutview.h @@ -306,6 +306,7 @@ class GUI_EXPORT QgsLayoutView: public QGraphicsView int mCurrentPage = 0; friend class TestQgsLayoutView; + friend class QgsLayoutMouseHandles; QGraphicsLineItem *createSnapLine() const; }; diff --git a/tests/src/python/test_qgslayoutsnapper.py b/tests/src/python/test_qgslayoutsnapper.py index fd41f3d59a3..3c483814d09 100644 --- a/tests/src/python/test_qgslayoutsnapper.py +++ b/tests/src/python/test_qgslayoutsnapper.py @@ -314,6 +314,11 @@ class TestQgsLayoutSnapper(unittest.TestCase): self.assertTrue(snapped) self.assertEqual(point, QPointF(0, 1.1)) + # ... unless item is ignored! + point, snapped = s.snapPoint(QPointF(1, 1), 1, None, None, [item1]) + self.assertTrue(snapped) + self.assertEqual(point, QPointF(0, 0)) + def testReadWriteXml(self): p = QgsProject() l = QgsLayout(p)