From e1bfad138f39cf3c6231755808372b5e32415d92 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 7 Aug 2024 12:33:46 +1000 Subject: [PATCH] Improve UX for modify annotation tool - When a selected item has bounds which depend on the map scale, update the selection handles after a canvas move/zoom occurs. Avoids showing ugly outdated bounding box for selected item - If no item is selected, recheck for potential hovered items after a canvas pan/zoom occurs. This correctly updates the hovered item bounds if there's now an item sitting under the mouse cursor --- .../qgsmaptoolmodifyannotation.cpp | 157 ++++++++++-------- .../annotations/qgsmaptoolmodifyannotation.h | 2 + 2 files changed, 91 insertions(+), 68 deletions(-) diff --git a/src/gui/annotations/qgsmaptoolmodifyannotation.cpp b/src/gui/annotations/qgsmaptoolmodifyannotation.cpp index 3c5f5fe43261..9e2f2671d1a2 100644 --- a/src/gui/annotations/qgsmaptoolmodifyannotation.cpp +++ b/src/gui/annotations/qgsmaptoolmodifyannotation.cpp @@ -124,6 +124,7 @@ void QgsMapToolModifyAnnotation::deactivate() void QgsMapToolModifyAnnotation::cadCanvasMoveEvent( QgsMapMouseEvent *event ) { + mLastHoverPoint = event->originalPixelPoint(); event->snapPoint(); mSnapIndicator->setMatch( event->mapPointMatch() ); @@ -137,73 +138,7 @@ void QgsMapToolModifyAnnotation::cadCanvasMoveEvent( QgsMapMouseEvent *event ) { case Action::NoAction: { - QgsRectangle searchRect = QgsRectangle( mapPoint.x(), mapPoint.y(), mapPoint.x(), mapPoint.y() ); - searchRect.grow( searchRadiusMU( canvas() ) ); - - const QgsRenderedItemResults *renderedItemResults = canvas()->renderedItemResults( false ); - if ( !renderedItemResults ) - { - clearHoveredItem(); - return; - } - - const QList items = renderedItemResults->renderedAnnotationItemsInBounds( searchRect ); - if ( items.empty() ) - { - clearHoveredItem(); - return; - } - - // find closest item - QgsRectangle itemBounds; - const QgsRenderedAnnotationItemDetails *closestItem = findClosestItemToPoint( mapPoint, items, itemBounds ); - if ( !closestItem ) - { - clearHoveredItem(); - return; - } - - if ( closestItem->itemId() != mHoveredItemId || closestItem->layerId() != mHoveredItemLayerId ) - { - setHoveredItem( closestItem, itemBounds ); - } - - // track hovered node too!... here we want to identify the closest node to the cursor position - QgsAnnotationItemNode hoveredNode; - if ( closestItem->itemId() == mSelectedItemId && closestItem->layerId() == mSelectedItemLayerId ) - { - double currentNodeDistance = std::numeric_limits< double >::max(); - mHoveredItemNodesSpatialIndex->intersects( searchRect, [&hoveredNode, ¤tNodeDistance, &mapPoint, this]( int index )-> bool - { - const QgsAnnotationItemNode &thisNode = mHoveredItemNodes.at( index ); - const double nodeDistance = thisNode.point().sqrDist( mapPoint ); - if ( nodeDistance < currentNodeDistance ) - { - hoveredNode = thisNode; - currentNodeDistance = nodeDistance; - } - return true; - } ); - } - - if ( hoveredNode.point().isEmpty() ) - { - // no hovered node - if ( mHoveredNodeRubberBand ) - mHoveredNodeRubberBand->hide(); - setCursor( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId ? Qt::OpenHandCursor : Qt::ArrowCursor ); - } - else - { - if ( !mHoveredNodeRubberBand ) - createHoveredNodeBand(); - - mHoveredNodeRubberBand->reset( Qgis::GeometryType::Point ); - mHoveredNodeRubberBand->addPoint( hoveredNode.point() ); - mHoveredNodeRubberBand->show(); - - setCursor( Qt::ArrowCursor ); - } + setHoveredItemFromPoint( mapPoint ); break; } @@ -574,7 +509,16 @@ void QgsMapToolModifyAnnotation::keyPressEvent( QKeyEvent *event ) void QgsMapToolModifyAnnotation::onCanvasRefreshed() { - if ( mRefreshSelectedItemAfterRedraw ) + bool needsSelectedItemRefresh = mRefreshSelectedItemAfterRedraw; + if ( QgsAnnotationItem *item = annotationItemFromId( mSelectedItemLayerId, mSelectedItemId ) ) + { + if ( item->flags() & Qgis::AnnotationItemFlag::ScaleDependentBoundingBox ) + { + needsSelectedItemRefresh = true; + } + } + + if ( needsSelectedItemRefresh ) { const QgsRenderedItemResults *renderedItemResults = canvas()->renderedItemResults( false ); if ( !renderedItemResults ) @@ -604,6 +548,12 @@ void QgsMapToolModifyAnnotation::onCanvasRefreshed() mSelectedRubberBand->show(); } } + else + { + // recheck for hovered item at new mouse point + const QgsPointXY mapPoint = canvas()->mapSettings().mapToPixel().toMapCoordinates( mLastHoverPoint ); + setHoveredItemFromPoint( mapPoint ); + } mRefreshSelectedItemAfterRedraw = false; } @@ -787,6 +737,77 @@ QgsAnnotationItem *QgsMapToolModifyAnnotation::annotationItemFromId( const QStri return layer ? layer->item( itemId ) : nullptr; } +void QgsMapToolModifyAnnotation::setHoveredItemFromPoint( const QgsPointXY &mapPoint ) +{ + QgsRectangle searchRect = QgsRectangle( mapPoint.x(), mapPoint.y(), mapPoint.x(), mapPoint.y() ); + searchRect.grow( searchRadiusMU( canvas() ) ); + + const QgsRenderedItemResults *renderedItemResults = canvas()->renderedItemResults( false ); + if ( !renderedItemResults ) + { + clearHoveredItem(); + return; + } + + const QList items = renderedItemResults->renderedAnnotationItemsInBounds( searchRect ); + if ( items.empty() ) + { + clearHoveredItem(); + return; + } + + // find closest item + QgsRectangle itemBounds; + const QgsRenderedAnnotationItemDetails *closestItem = findClosestItemToPoint( mapPoint, items, itemBounds ); + if ( !closestItem ) + { + clearHoveredItem(); + return; + } + + if ( closestItem->itemId() != mHoveredItemId || closestItem->layerId() != mHoveredItemLayerId ) + { + setHoveredItem( closestItem, itemBounds ); + } + + // track hovered node too!... here we want to identify the closest node to the cursor position + QgsAnnotationItemNode hoveredNode; + if ( closestItem->itemId() == mSelectedItemId && closestItem->layerId() == mSelectedItemLayerId ) + { + double currentNodeDistance = std::numeric_limits< double >::max(); + mHoveredItemNodesSpatialIndex->intersects( searchRect, [&hoveredNode, ¤tNodeDistance, &mapPoint, this]( int index )-> bool + { + const QgsAnnotationItemNode &thisNode = mHoveredItemNodes.at( index ); + const double nodeDistance = thisNode.point().sqrDist( mapPoint ); + if ( nodeDistance < currentNodeDistance ) + { + hoveredNode = thisNode; + currentNodeDistance = nodeDistance; + } + return true; + } ); + } + + if ( hoveredNode.point().isEmpty() ) + { + // no hovered node + if ( mHoveredNodeRubberBand ) + mHoveredNodeRubberBand->hide(); + setCursor( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId ? Qt::OpenHandCursor : Qt::ArrowCursor ); + } + else + { + if ( !mHoveredNodeRubberBand ) + createHoveredNodeBand(); + + mHoveredNodeRubberBand->reset( Qgis::GeometryType::Point ); + mHoveredNodeRubberBand->addPoint( hoveredNode.point() ); + mHoveredNodeRubberBand->show(); + + setCursor( Qt::ArrowCursor ); + } +} + void QgsMapToolModifyAnnotation::clearHoveredItem() { if ( mHoverRubberBand ) diff --git a/src/gui/annotations/qgsmaptoolmodifyannotation.h b/src/gui/annotations/qgsmaptoolmodifyannotation.h index ed17750d7d15..16c37790a550 100644 --- a/src/gui/annotations/qgsmaptoolmodifyannotation.h +++ b/src/gui/annotations/qgsmaptoolmodifyannotation.h @@ -91,6 +91,7 @@ class GUI_EXPORT QgsMapToolModifyAnnotation : public QgsMapToolAdvancedDigitizin QgsAnnotationLayer *annotationLayerFromId( const QString &layerId ); QgsAnnotationItem *annotationItemFromId( const QString &layerId, const QString &itemId ); + void setHoveredItemFromPoint( const QgsPointXY &mapPoint ); void setHoveredItem( const QgsRenderedAnnotationItemDetails *item, const QgsRectangle &itemMapBounds ); /** @@ -114,6 +115,7 @@ class GUI_EXPORT QgsMapToolModifyAnnotation : public QgsMapToolAdvancedDigitizin QObjectUniquePtr mSelectedRubberBand; QObjectUniquePtr mTemporaryRubberBand; + QPoint mLastHoverPoint; QString mHoveredItemId; QString mHoveredItemLayerId; QgsRectangle mHoveredItemBounds;