Add search tolerance to QgsLayoutViewToolSelect

This commit is contained in:
Yoann Quenach de Quivillic 2023-06-13 00:41:04 +02:00 committed by Nyall Dawson
parent c431054d84
commit 8f3676dd80
9 changed files with 75 additions and 19 deletions

View File

@ -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 );

View File

@ -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
};

View File

@ -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<QGraphicsItem *> itemList = items( position, Qt::IntersectsItemShape, Qt::DescendingOrder );
QList<QGraphicsItem *> 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 )

View File

@ -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

View File

@ -99,7 +99,17 @@ void QgsLayoutMouseHandles::setViewportCursor( Qt::CursorShape cursor )
QList<QGraphicsItem *> 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<QgsLayoutItem *>( item );

View File

@ -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<QGraphicsItem *> 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

View File

@ -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

View File

@ -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 )

View File

@ -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