diff --git a/python/core/auto_generated/layout/qgslayout.sip.in b/python/core/auto_generated/layout/qgslayout.sip.in index f2f094aaf9b..7171c369ad1 100644 --- a/python/core/auto_generated/layout/qgslayout.sip.in +++ b/python/core/auto_generated/layout/qgslayout.sip.in @@ -237,16 +237,20 @@ Note that template UUIDs are only available while a layout is being restored fro .. seealso:: :py:func:`itemByUuid` %End - QgsLayoutItem *layoutItemAt( QPointF position, bool ignoreLocked = false ) const; + QgsLayoutItem *layoutItemAt( QPointF position, bool ignoreLocked = false, double searchTolerance = 0 ) const; %Docstring Returns the topmost layout item at a specified ``position``. Ignores paper items. If ``ignoreLocked`` is set to ``True`` any locked items will be ignored. + +.. versionadded:: 3.32 %End - QgsLayoutItem *layoutItemAt( QPointF position, const QgsLayoutItem *belowItem, bool ignoreLocked = false ) const; + QgsLayoutItem *layoutItemAt( QPointF position, const QgsLayoutItem *belowItem, bool ignoreLocked = false, double searchTolerance = 0 ) const; %Docstring Returns the topmost layout item at a specified ``position`` which is below a specified ``item``. Ignores paper items. If ``ignoreLocked`` is set to ``True`` any locked items will be ignored. + +.. versionadded:: 3.32 %End void setUnits( Qgis::LayoutUnit units ); diff --git a/python/gui/auto_generated/layout/qgslayoutviewtoolselect.sip.in b/python/gui/auto_generated/layout/qgslayoutviewtoolselect.sip.in index 19303c51bb3..a20975dc8b4 100644 --- a/python/gui/auto_generated/layout/qgslayoutviewtoolselect.sip.in +++ b/python/gui/auto_generated/layout/qgslayoutviewtoolselect.sip.in @@ -46,6 +46,13 @@ Constructor for QgsLayoutViewToolSelect. void setLayout( QgsLayout *layout ); %Docstring Sets the a ``layout``. +%End + + double searchToleranceInLayoutUnits(); +%Docstring +Compute the search tolerance in layout units from the view current scale + +.. versionadded:: 3.34 %End }; diff --git a/src/core/layout/qgslayout.cpp b/src/core/layout/qgslayout.cpp index 0eca5f6a6b8..d3a8d005b22 100644 --- a/src/core/layout/qgslayout.cpp +++ b/src/core/layout/qgslayout.cpp @@ -298,15 +298,23 @@ QgsLayoutMultiFrame *QgsLayout::multiFrameByUuid( const QString &uuid, bool incl return nullptr; } -QgsLayoutItem *QgsLayout::layoutItemAt( QPointF position, const bool ignoreLocked ) const +QgsLayoutItem *QgsLayout::layoutItemAt( QPointF position, const bool ignoreLocked, double searchTolerance ) const { - return layoutItemAt( position, nullptr, ignoreLocked ); + return layoutItemAt( position, nullptr, ignoreLocked, searchTolerance ); } -QgsLayoutItem *QgsLayout::layoutItemAt( QPointF position, const QgsLayoutItem *belowItem, const bool ignoreLocked ) const +QgsLayoutItem *QgsLayout::layoutItemAt( QPointF position, const QgsLayoutItem *belowItem, const bool ignoreLocked, double searchTolerance ) const { //get a list of items which intersect the specified position, in descending z order - const QList itemList = items( position, Qt::IntersectsItemShape, Qt::DescendingOrder ); + QList itemList; + if ( searchTolerance == 0 ) + { + itemList = items( position, Qt::IntersectsItemShape, Qt::DescendingOrder ); + } + else + { + itemList = items( QRectF( position.x() - searchTolerance, position.y() - searchTolerance, 2 * searchTolerance, 2 * searchTolerance ), Qt::IntersectsItemShape, Qt::DescendingOrder ); + } bool foundBelowItem = false; for ( QGraphicsItem *graphicsItem : itemList ) diff --git a/src/core/layout/qgslayout.h b/src/core/layout/qgslayout.h index 007438adef2..9691aaa1a2f 100644 --- a/src/core/layout/qgslayout.h +++ b/src/core/layout/qgslayout.h @@ -303,14 +303,16 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext /** * Returns the topmost layout item at a specified \a position. Ignores paper items. * If \a ignoreLocked is set to TRUE any locked items will be ignored. + * \since QGIS 3.32 searchTolerance parameter was added, which can be used to specify a search tolerance in layout units */ - QgsLayoutItem *layoutItemAt( QPointF position, bool ignoreLocked = false ) const; + QgsLayoutItem *layoutItemAt( QPointF position, bool ignoreLocked = false, double searchTolerance = 0 ) const; /** * Returns the topmost layout item at a specified \a position which is below a specified \a item. Ignores paper items. * If \a ignoreLocked is set to TRUE any locked items will be ignored. + * \since QGIS 3.32 searchTolerance parameter was added, which can be used to specify a search tolerance in layout units */ - QgsLayoutItem *layoutItemAt( QPointF position, const QgsLayoutItem *belowItem, bool ignoreLocked = false ) const; + QgsLayoutItem *layoutItemAt( QPointF position, const QgsLayoutItem *belowItem, bool ignoreLocked = false, double searchTolerance = 0 ) const; /** * Sets the native measurement \a units for the layout. These also form the default unit diff --git a/src/gui/layout/qgslayoutmousehandles.cpp b/src/gui/layout/qgslayoutmousehandles.cpp index f589a91d8ad..c598dc03025 100644 --- a/src/gui/layout/qgslayoutmousehandles.cpp +++ b/src/gui/layout/qgslayoutmousehandles.cpp @@ -99,7 +99,17 @@ void QgsLayoutMouseHandles::setViewportCursor( Qt::CursorShape cursor ) QList QgsLayoutMouseHandles::sceneItemsAtPoint( QPointF scenePoint ) { - QList< QGraphicsItem * > items = mLayout->items( scenePoint ); + QList< QGraphicsItem * > items; + if ( QgsLayoutViewToolSelect *tool = qobject_cast< QgsLayoutViewToolSelect *>( mView->tool() ) ) + { + const double searchTolerance = tool->searchToleranceInLayoutUnits(); + const QRectF area( scenePoint.x() - searchTolerance, scenePoint.y() - searchTolerance, 2 * searchTolerance, 2 * searchTolerance ); + items = mLayout->items( area ); + } + else + { + items = mLayout->items( scenePoint ); + } items.erase( std::remove_if( items.begin(), items.end(), []( QGraphicsItem * item ) { return !dynamic_cast( item ); diff --git a/src/gui/layout/qgslayoutviewtoolselect.cpp b/src/gui/layout/qgslayoutviewtoolselect.cpp index c8244247608..feffdeea75d 100644 --- a/src/gui/layout/qgslayoutviewtoolselect.cpp +++ b/src/gui/layout/qgslayoutviewtoolselect.cpp @@ -22,6 +22,9 @@ #include "qgslayoutitemgroup.h" +const double QgsLayoutViewToolSelect::sSearchToleranceInMillimeters = 2.0; + + QgsLayoutViewToolSelect::QgsLayoutViewToolSelect( QgsLayoutView *view ) : QgsLayoutViewTool( view, tr( "Select" ) ) { @@ -94,19 +97,19 @@ void QgsLayoutViewToolSelect::layoutPressEvent( QgsLayoutViewMouseEvent *event ) if ( previousSelectedItem ) { //select highest item just below previously selected item at position of event - selectedItem = layout()->layoutItemAt( event->layoutPoint(), previousSelectedItem, true ); + selectedItem = layout()->layoutItemAt( event->layoutPoint(), previousSelectedItem, true, searchToleranceInLayoutUnits() ); //if we didn't find a lower item we'll use the top-most as fall-back //this duplicates mapinfo/illustrator/etc behavior where ctrl-clicks are "cyclic" if ( !selectedItem ) { - selectedItem = layout()->layoutItemAt( event->layoutPoint(), true ); + selectedItem = layout()->layoutItemAt( event->layoutPoint(), true, searchToleranceInLayoutUnits() ); } } else { //select topmost item at position of event - selectedItem = layout()->layoutItemAt( event->layoutPoint(), true ); + selectedItem = layout()->layoutItemAt( event->layoutPoint(), true, searchToleranceInLayoutUnits() ); } // if selected item is in a group, we actually get the top-level group it's part of @@ -167,6 +170,17 @@ void QgsLayoutViewToolSelect::layoutMoveEvent( QgsLayoutViewMouseEvent *event ) } else { + if ( !mMouseHandles->isDragging() && !mMouseHandles->isResizing() ) + { + if ( layout()->layoutItemAt( event->layoutPoint(), true, searchToleranceInLayoutUnits() ) ) + { + view()->viewport()->setCursor( Qt::SizeAllCursor ); + } + else + { + view()->viewport()->setCursor( Qt::ArrowCursor ); + } + } event->ignore(); } } @@ -219,7 +233,10 @@ void QgsLayoutViewToolSelect::layoutReleaseEvent( QgsLayoutViewMouseEvent *event //find all items in rect QList itemList; if ( wasClick ) - itemList = layout()->items( rect.center(), selectionMode ); + { + const double tolerance = searchToleranceInLayoutUnits(); + itemList = layout()->items( QRectF( rect.center().x() - tolerance, rect.center().y() - tolerance, 2 * tolerance, 2 * tolerance ), selectionMode ); + } else itemList = layout()->items( rect, selectionMode ); @@ -323,4 +340,9 @@ void QgsLayoutViewToolSelect::setLayout( QgsLayout *layout ) mMouseHandles->setZValue( QgsLayout::ZMouseHandles ); layout->addItem( mMouseHandles ); } +double QgsLayoutViewToolSelect::searchToleranceInLayoutUnits() +{ + const double pixelsPerMm = view()->physicalDpiX() / 25.4; + return sSearchToleranceInMillimeters * pixelsPerMm / view()->transform().m11(); +} ///@endcond diff --git a/src/gui/layout/qgslayoutviewtoolselect.h b/src/gui/layout/qgslayoutviewtoolselect.h index 26ea958a1f7..494de431c90 100644 --- a/src/gui/layout/qgslayoutviewtoolselect.h +++ b/src/gui/layout/qgslayoutviewtoolselect.h @@ -61,6 +61,12 @@ class GUI_EXPORT QgsLayoutViewToolSelect : public QgsLayoutViewTool //! Sets the a \a layout. void setLayout( QgsLayout *layout ); + /** + * Compute the search tolerance in layout units from the view current scale + * \since QGIS 3.34 + */ + double searchToleranceInLayoutUnits(); + private: bool mIsSelecting = false; @@ -75,6 +81,9 @@ class GUI_EXPORT QgsLayoutViewToolSelect : public QgsLayoutViewTool QPointF mRubberBandStartPos; QPointer< QgsLayoutMouseHandles > mMouseHandles; //owned by scene + + //! Search tolerance in millimeters for selecting items + static const double sSearchToleranceInMillimeters; }; #endif // QGSLAYOUTVIEWTOOLSELECT_H diff --git a/src/gui/qgsgraphicsviewmousehandles.cpp b/src/gui/qgsgraphicsviewmousehandles.cpp index 1f2c4d46a2b..39389982205 100644 --- a/src/gui/qgsgraphicsviewmousehandles.cpp +++ b/src/gui/qgsgraphicsviewmousehandles.cpp @@ -465,7 +465,6 @@ void QgsGraphicsViewMouseHandles::mousePressEvent( QGraphicsSceneMouseEvent *eve //save current cursor position mMouseMoveStartPos = event->lastScenePos(); - mLastMouseEventPos = event->lastScenePos(); //save current item geometry mBeginMouseEventPos = event->lastScenePos(); mBeginHandlePos = scenePos(); @@ -526,8 +525,6 @@ void QgsGraphicsViewMouseHandles::mouseMoveEvent( QGraphicsSceneMouseEvent *even //resize from center if alt depressed resizeMouseMove( event->lastScenePos(), event->modifiers() & Qt::ShiftModifier, event->modifiers() & Qt::AltModifier ); } - - mLastMouseEventPos = event->lastScenePos(); } void QgsGraphicsViewMouseHandles::mouseReleaseEvent( QGraphicsSceneMouseEvent *event ) diff --git a/src/gui/qgsgraphicsviewmousehandles.h b/src/gui/qgsgraphicsviewmousehandles.h index f6cee34a61c..99aad3972e1 100644 --- a/src/gui/qgsgraphicsviewmousehandles.h +++ b/src/gui/qgsgraphicsviewmousehandles.h @@ -216,9 +216,6 @@ class GUI_EXPORT QgsGraphicsViewMouseHandles: public QObject, public QGraphicsRe //! Start point of the last mouse move action (in scene coordinates) QPointF mMouseMoveStartPos; - //! Position of the last mouse move event (in scene coordinates) - QPointF mLastMouseEventPos; - MouseAction mCurrentMouseMoveAction = NoAction; //! True if user is currently dragging items