37#include "moc_qgsmaptoolmodifyannotation.cpp"
40class QgsAnnotationItemNodesSpatialIndex :
public RTree<int, float, 2, float>
43 void insert(
int index,
const QgsRectangle &bounds )
45 std::array<float, 4> scaledBounds = scaleBounds( bounds );
47 scaledBounds[0], scaledBounds[1]
50 scaledBounds[2], scaledBounds[3]
65 void remove(
int index,
const QgsRectangle &bounds )
67 std::array<float, 4> scaledBounds = scaleBounds( bounds );
69 scaledBounds[0], scaledBounds[1]
72 scaledBounds[2], scaledBounds[3]
86 bool intersects(
const QgsRectangle &bounds,
const std::function<
bool(
int index )> &callback )
const
88 std::array<float, 4> scaledBounds = scaleBounds( bounds );
90 scaledBounds[0], scaledBounds[1]
93 scaledBounds[2], scaledBounds[3]
104 std::array<float, 4> scaleBounds(
const QgsRectangle &bounds )
const
107 static_cast<float>( bounds.
xMinimum() ),
108 static_cast<float>( bounds.
yMinimum() ),
109 static_cast<float>( bounds.
xMaximum() ),
110 static_cast<float>( bounds.
yMaximum() )
137 mLastHoverPoint =
event->originalPixelPoint();
141 const QgsPointXY mapPoint =
event->mapPoint();
148 switch ( mCurrentAction )
150 case Action::NoAction:
152 setHoveredItemFromPoint( mapPoint );
156 case Action::MoveItem:
158 if (
QgsAnnotationItem *item = annotationItemFromId( mSelectedItemLayerId, mSelectedItemId ) )
163 std::unique_ptr<QgsAnnotationItemEditOperationTransientResults> operationResults( item->transientEditResultsV2( &operation, context ) );
164 if ( operationResults )
166 mTemporaryRubberBand.reset(
new QgsRubberBand(
mCanvas, operationResults->representativeGeometry().type() ) );
167 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
168 mTemporaryRubberBand->setWidth( scaleFactor );
169 mTemporaryRubberBand->setToGeometry( operationResults->representativeGeometry(),
layer->crs() );
173 mTemporaryRubberBand.reset();
179 case Action::MoveNode:
181 if (
QgsAnnotationItem *item = annotationItemFromId( mSelectedItemLayerId, mSelectedItemId ) )
185 std::unique_ptr<QgsAnnotationItemEditOperationTransientResults> operationResults( item->transientEditResultsV2( &operation, context ) );
186 if ( operationResults )
188 mTemporaryRubberBand.reset(
new QgsRubberBand(
mCanvas, operationResults->representativeGeometry().type() ) );
189 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
190 mTemporaryRubberBand->setWidth( scaleFactor );
191 mTemporaryRubberBand->setToGeometry( operationResults->representativeGeometry(),
layer->crs() );
195 mTemporaryRubberBand.reset();
211 switch ( mCurrentAction )
213 case Action::NoAction:
215 if ( event->button() != Qt::LeftButton )
218 if ( mHoveredItemId.isEmpty() || !mHoverRubberBand )
222 else if ( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId )
227 const QgsPointXY mapPoint =
event->mapPoint();
232 double currentNodeDistance = std::numeric_limits<double>::max();
233 mHoveredItemNodesSpatialIndex->intersects( searchRect, [&hoveredNode, ¤tNodeDistance, &mapPoint,
this](
int index ) ->
bool {
235 const double nodeDistance = thisNode.
point().
sqrDist( mapPoint );
236 if ( nodeDistance < currentNodeDistance )
238 hoveredNode = thisNode;
239 currentNodeDistance = nodeDistance;
244 mMoveStartPointCanvasCrs = mapPoint;
245 mMoveStartPointPixels =
event->pixelPoint();
247 if ( mHoverRubberBand )
248 mHoverRubberBand->hide();
249 if ( mSelectedRubberBand )
250 mSelectedRubberBand->hide();
254 mCurrentAction = Action::MoveItem;
258 mCurrentAction = Action::MoveNode;
259 mTargetNode = hoveredNode;
266 mSelectedItemId = mHoveredItemId;
267 mSelectedItemLayerId = mHoveredItemLayerId;
268 mSelectedItemBounds = mHoveredItemBounds;
270 if ( !mSelectedRubberBand )
271 createSelectedItemBand();
273 mSelectedRubberBand->copyPointsFrom( mHoverRubberBand );
274 mSelectedRubberBand->show();
278 emit
itemSelected( annotationLayerFromId( mSelectedItemLayerId ), mSelectedItemId );
283 case Action::MoveItem:
285 if ( event->button() == Qt::RightButton )
287 mCurrentAction = Action::NoAction;
288 mTemporaryRubberBand.reset();
289 if ( mSelectedRubberBand )
291 mSelectedRubberBand->setTranslationOffset( 0, 0 );
292 mSelectedRubberBand->show();
294 mHoveredItemNodeRubberBands.clear();
297 else if ( event->button() == Qt::LeftButton )
305 switch (
layer->applyEditV2( &operation, context ) )
309 mRefreshSelectedItemAfterRedraw =
true;
317 mTemporaryRubberBand.reset();
318 mCurrentAction = Action::NoAction;
324 case Action::MoveNode:
326 if ( event->button() == Qt::RightButton )
328 mCurrentAction = Action::NoAction;
329 mTemporaryRubberBand.reset();
330 mHoveredItemNodeRubberBands.clear();
331 mTemporaryRubberBand.reset();
334 else if ( event->button() == Qt::LeftButton )
340 switch (
layer->applyEditV2( &operation, context ) )
344 mRefreshSelectedItemAfterRedraw =
true;
353 mTemporaryRubberBand.reset();
354 mHoveredItemNodeRubberBands.clear();
355 mHoveredItemNodes.clear();
356 mTemporaryRubberBand.reset();
357 mCurrentAction = Action::NoAction;
367 switch ( mCurrentAction )
369 case Action::NoAction:
370 case Action::MoveItem:
372 if ( event->button() != Qt::LeftButton )
375 mCurrentAction = Action::NoAction;
376 if ( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId )
387 switch (
layer->applyEditV2( &operation, context ) )
391 mRefreshSelectedItemAfterRedraw =
true;
403 mSelectedItemId = mHoveredItemId;
404 mSelectedItemLayerId = mHoveredItemLayerId;
405 mSelectedItemBounds = mHoveredItemBounds;
407 if ( !mSelectedRubberBand )
408 createSelectedItemBand();
410 mSelectedRubberBand->copyPointsFrom( mHoverRubberBand );
411 mSelectedRubberBand->show();
415 emit
itemSelected( annotationLayerFromId( mSelectedItemLayerId ), mSelectedItemId );
420 case Action::MoveNode:
432 switch ( mCurrentAction )
434 case Action::NoAction:
436 if ( event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete )
438 if ( !
layer || mSelectedItemId.isEmpty() )
441 layer->removeItem( mSelectedItemId );
446 else if ( event->key() == Qt::Key_Left
447 || event->key() == Qt::Key_Right
448 || event->key() == Qt::Key_Up
449 || event->key() == Qt::Key_Down )
454 const QSizeF deltaLayerCoordinates = deltaForKeyEvent(
layer, mSelectedRubberBand->asGeometry().centroid().asPoint(), event );
457 switch (
layer->applyEditV2( &operation, context ) )
461 mRefreshSelectedItemAfterRedraw =
true;
472 case Action::MoveNode:
474 if ( event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace )
479 switch (
layer->applyEditV2( &operation, context ) )
483 mRefreshSelectedItemAfterRedraw =
true;
493 mTemporaryRubberBand.reset();
494 mHoveredItemNodeRubberBands.clear();
495 mHoveredItemNodes.clear();
496 mTemporaryRubberBand.reset();
497 mCurrentAction = Action::NoAction;
505 case Action::MoveItem:
508 if ( event->key() == Qt::Key_Escape )
510 mCurrentAction = Action::NoAction;
511 mTemporaryRubberBand.reset();
512 if ( mSelectedRubberBand )
514 mSelectedRubberBand->setTranslationOffset( 0, 0 );
515 mSelectedRubberBand->show();
517 mHoveredItemNodeRubberBands.clear();
526void QgsMapToolModifyAnnotation::onCanvasRefreshed()
528 bool needsSelectedItemRefresh = mRefreshSelectedItemAfterRedraw;
529 if (
QgsAnnotationItem *item = annotationItemFromId( mSelectedItemLayerId, mSelectedItemId ) )
533 needsSelectedItemRefresh =
true;
537 if ( needsSelectedItemRefresh )
540 if ( !renderedItemResults )
545 const QList<QgsRenderedItemDetails *> items = renderedItemResults->
renderedItems();
546 auto it = std::find_if( items.begin(), items.end(), [
this](
const QgsRenderedItemDetails *item ) {
547 if ( const QgsRenderedAnnotationItemDetails *annotationItem = dynamic_cast<const QgsRenderedAnnotationItemDetails *>( item ) )
549 if ( annotationItem->itemId() == mSelectedItemId && annotationItem->layerId() == mSelectedItemLayerId )
554 if ( it != items.end() )
556 const QgsRectangle itemBounds = ( *it )->boundingBox();
558 setHoveredItem(
dynamic_cast<const QgsRenderedAnnotationItemDetails *
>( *it ), itemBounds );
559 if ( !mSelectedRubberBand )
560 createSelectedItemBand();
562 mSelectedRubberBand->copyPointsFrom( mHoverRubberBand );
563 mSelectedRubberBand->show();
564 mSelectedItemBounds = mHoveredItemBounds;
570 const QgsPointXY mapPoint = canvas()->mapSettings().mapToPixel().toMapCoordinates( mLastHoverPoint );
571 setHoveredItemFromPoint( mapPoint );
573 mRefreshSelectedItemAfterRedraw =
false;
578 mHoveredItemNodeRubberBands.clear();
579 if ( mHoveredNodeRubberBand )
580 mHoveredNodeRubberBand->hide();
581 mHoveredItemId = item->
itemId();
582 mHoveredItemLayerId = item->
layerId();
583 mHoveredItemBounds = itemMapBounds;
584 if ( !mHoverRubberBand )
587 mHoverRubberBand->show();
590 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.
xMinimum(), itemMapBounds.
yMinimum() ) );
591 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.
xMaximum(), itemMapBounds.
yMinimum() ) );
592 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.
xMaximum(), itemMapBounds.
yMaximum() ) );
593 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.
xMinimum(), itemMapBounds.
yMaximum() ) );
594 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.
xMinimum(), itemMapBounds.
yMinimum() ) );
596 QgsAnnotationLayer *
layer = annotationLayerFromId( item->
layerId() );
597 const QgsAnnotationItem *annotationItem = annotationItemFromId( item->
layerId(), item->
itemId() );
598 if ( !annotationItem )
601 QgsCoordinateTransform layerToMapTransform = QgsCoordinateTransform(
layer->crs(),
canvas()->mapSettings().destinationCrs(),
canvas()->mapSettings().transformContext() );
603 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
605 QgsAnnotationItemEditContext context;
609 const QList<QgsAnnotationItemNode> itemNodes = annotationItem->
nodesV2( context );
613 vertexNodeBand->
setWidth( scaleFactor );
615 vertexNodeBand->
setColor( QColor( 200, 0, 120, 255 ) );
618 calloutNodeBand->
setWidth( scaleFactor );
620 calloutNodeBand->
setColor( QColor( 120, 200, 0, 255 ) );
625 mHoveredItemNodesSpatialIndex = std::make_unique<QgsAnnotationItemNodesSpatialIndex>();
627 mHoveredItemNodes.clear();
628 mHoveredItemNodes.reserve( itemNodes.size() );
629 for (
const QgsAnnotationItemNode &node : itemNodes )
631 QgsPointXY nodeMapPoint;
634 nodeMapPoint = layerToMapTransform.
transform( node.point() );
636 catch ( QgsCsException & )
641 switch ( node.type() )
644 vertexNodeBand->
addPoint( nodeMapPoint );
648 calloutNodeBand->
addPoint( nodeMapPoint );
652 mHoveredItemNodesSpatialIndex->insert( index, QgsRectangle( nodeMapPoint.
x(), nodeMapPoint.
y(), nodeMapPoint.
x(), nodeMapPoint.
y() ) );
654 QgsAnnotationItemNode transformedNode = node;
655 transformedNode.
setPoint( nodeMapPoint );
656 mHoveredItemNodes.append( transformedNode );
661 mHoveredItemNodeRubberBands.emplace_back( vertexNodeBand );
662 mHoveredItemNodeRubberBands.emplace_back( calloutNodeBand );
665QSizeF QgsMapToolModifyAnnotation::deltaForKeyEvent(
QgsAnnotationLayer *layer,
const QgsPointXY &originalCanvasPoint, QKeyEvent *event )
667 const double canvasDpi =
canvas()->window()->windowHandle()->screen()->physicalDotsPerInch();
670 double incrementPixels = 0.0;
671 if ( event->modifiers() & Qt::ShiftModifier )
674 incrementPixels = 20.0 / 25.4 * canvasDpi;
676 else if ( event->modifiers() & Qt::AltModifier )
684 incrementPixels = 5.0 / 25.4 * canvasDpi;
687 double deltaXPixels = 0;
688 double deltaYPixels = 0;
689 switch ( event->key() )
692 deltaXPixels = -incrementPixels;
695 deltaXPixels = incrementPixels;
698 deltaYPixels = -incrementPixels;
701 deltaYPixels = incrementPixels;
710 const QgsPointXY afterMoveCanvasPoint( originalCanvasPoint.
x() + deltaXPixels, originalCanvasPoint.
y() + deltaYPixels );
714 return QSizeF( afterMoveLayerPoint.
x() - beforeMoveLayerPoint.
x(), afterMoveLayerPoint.
y() - beforeMoveLayerPoint.
y() );
719 const QgsRenderedAnnotationItemDetails *closestItem =
nullptr;
720 double closestItemDistance = std::numeric_limits<double>::max();
721 double closestItemArea = std::numeric_limits<double>::max();
723 for (
const QgsRenderedAnnotationItemDetails *item : items )
725 const QgsAnnotationItem *annotationItem = annotationItemFromId( item->
layerId(), item->
itemId() );
726 if ( !annotationItem )
729 const QgsRectangle itemBounds = item->
boundingBox();
730 const double itemDistance = itemBounds.
contains( mapPoint ) ? 0 : itemBounds.
distance( mapPoint );
731 if ( !closestItem || itemDistance < closestItemDistance || ( itemDistance == closestItemDistance && itemBounds.
area() < closestItemArea ) )
734 closestItemDistance = itemDistance;
735 closestItemArea = itemBounds.
area();
742QgsAnnotationLayer *QgsMapToolModifyAnnotation::annotationLayerFromId(
const QString &layerId )
750QgsAnnotationItem *QgsMapToolModifyAnnotation::annotationItemFromId(
const QString &layerId,
const QString &itemId )
752 QgsAnnotationLayer *
layer = annotationLayerFromId( layerId );
753 return layer ?
layer->item( itemId ) :
nullptr;
756void QgsMapToolModifyAnnotation::setHoveredItemFromPoint(
const QgsPointXY &mapPoint )
758 QgsRectangle searchRect = QgsRectangle( mapPoint.
x(), mapPoint.
y(), mapPoint.
x(), mapPoint.
y() );
762 if ( !renderedItemResults )
776 QgsRectangle itemBounds;
777 const QgsRenderedAnnotationItemDetails *closestItem = findClosestItemToPoint( mapPoint, items, itemBounds );
784 if ( closestItem->
itemId() != mHoveredItemId || closestItem->
layerId() != mHoveredItemLayerId )
786 setHoveredItem( closestItem, itemBounds );
790 QgsAnnotationItemNode hoveredNode;
791 if ( closestItem->
itemId() == mSelectedItemId && closestItem->
layerId() == mSelectedItemLayerId )
793 double currentNodeDistance = std::numeric_limits<double>::max();
794 mHoveredItemNodesSpatialIndex->intersects( searchRect, [&hoveredNode, ¤tNodeDistance, &mapPoint,
this](
int index ) ->
bool {
795 if ( index >= mHoveredItemNodes.size() )
798 const QgsAnnotationItemNode &thisNode = mHoveredItemNodes.at( index );
799 const double nodeDistance = thisNode.
point().
sqrDist( mapPoint );
800 if ( nodeDistance < currentNodeDistance )
802 hoveredNode = thisNode;
803 currentNodeDistance = nodeDistance;
812 if ( mHoveredNodeRubberBand )
813 mHoveredNodeRubberBand->hide();
814 setCursor( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId ? Qt::OpenHandCursor : Qt::ArrowCursor );
818 if ( !mHoveredNodeRubberBand )
819 createHoveredNodeBand();
822 mHoveredNodeRubberBand->addPoint( hoveredNode.
point() );
823 mHoveredNodeRubberBand->show();
829void QgsMapToolModifyAnnotation::clearHoveredItem()
831 if ( mHoverRubberBand )
832 mHoverRubberBand->hide();
833 if ( mHoveredNodeRubberBand )
834 mHoveredNodeRubberBand->hide();
836 mHoveredItemId.clear();
837 mHoveredItemLayerId.clear();
838 mHoveredItemNodeRubberBands.clear();
839 mHoveredItemNodesSpatialIndex.reset();
844void QgsMapToolModifyAnnotation::clearSelectedItem()
846 if ( mSelectedRubberBand )
847 mSelectedRubberBand->hide();
849 const bool hadSelection = !mSelectedItemId.isEmpty();
850 mSelectedItemId.clear();
851 mSelectedItemLayerId.clear();
856void QgsMapToolModifyAnnotation::createHoverBand()
858 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
861 mHoverRubberBand->setWidth( scaleFactor );
862 mHoverRubberBand->setSecondaryStrokeColor( QColor( 255, 255, 255, 100 ) );
863 mHoverRubberBand->setColor( QColor( 100, 100, 100, 155 ) );
866void QgsMapToolModifyAnnotation::createHoveredNodeBand()
868 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
872 mHoveredNodeRubberBand->setWidth( scaleFactor );
873 mHoveredNodeRubberBand->setIconSize( scaleFactor * 5 );
874 mHoveredNodeRubberBand->setColor( QColor( 200, 0, 120, 255 ) );
877void QgsMapToolModifyAnnotation::createSelectedItemBand()
879 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
882 mSelectedRubberBand->setWidth( scaleFactor );
883 mSelectedRubberBand->setSecondaryStrokeColor( QColor( 255, 255, 255, 100 ) );
884 mSelectedRubberBand->setColor( QColor( 50, 50, 50, 200 ) );
@ VertexHandle
Node is a handle for manipulating vertices.
@ CalloutHandle
Node is a handle for manipulating callouts.
@ ScaleDependentBoundingBox
Item's bounding box will vary depending on map scale.
@ Invalid
Operation has invalid parameters for the item, no change occurred.
@ Success
Item was modified successfully.
@ ItemCleared
The operation results in the item being cleared, and the item should be removed from the layer as a r...
Encapsulates the context for an annotation item edit operation.
void setCurrentItemBounds(const QgsRectangle &bounds)
Sets the current rendered bounds of the item, in the annotation layer's CRS.
void setRenderContext(const QgsRenderContext &context)
Sets the render context associated with the edit operation.
Annotation item edit operation consisting of adding a node.
Annotation item edit operation consisting of deleting a node.
Annotation item edit operation consisting of moving a node.
Annotation item edit operation consisting of translating (moving) an item.
Contains information about a node used for editing an annotation item.
void setPoint(QgsPointXY point)
Sets the node's position, in geographic coordinates.
QgsPointXY point() const
Returns the node's position, in geographic coordinates.
Qt::CursorShape cursor() const
Returns the mouse cursor shape to use when hovering the node.
Abstract base class for annotation items which are drawn with QgsAnnotationLayers.
virtual QList< QgsAnnotationItemNode > nodesV2(const QgsAnnotationItemEditContext &context) const
Returns the nodes for the item, used for editing the item.
Represents a map layer containing a set of georeferenced annotations, e.g.
const QgsRenderedItemResults * renderedItemResults(bool allowOutdatedResults=true) const
Gets access to the rendered item results (may be nullptr), which includes the results of rendering an...
void mapCanvasRefreshed()
Emitted when canvas finished a refresh request.
const QgsMapToPixel * getCoordinateTransform()
Gets the current coordinate transform.
A mouse event which is the result of a user interaction with a QgsMapCanvas.
QgsPointXY mapPoint() const
mapPoint returns the point in coordinates
QPoint pixelPoint() const
The snapped mouse cursor in pixel coordinates.
QgsPointLocator::Match mapPointMatch() const
Returns the matching data from the most recently snapped point.
QgsPointXY toMapCoordinates(int x, int y) const
Transforms device coordinates to map (world) coordinates.
double sqrDist(double x, double y) const
Returns the squared distance between this point a specified x, y coordinate.
bool isEmpty() const
Returns true if the geometry is empty.
Point geometry type, with support for z-dimension and m-values.
static QgsProject * instance()
Returns the QgsProject singleton instance.
QgsAnnotationLayer * mainAnnotationLayer()
Returns the main annotation layer associated with the project.
void setDirty(bool b=true)
Flag the project as dirty (modified).
A rectangle specified with double values.
bool contains(const QgsRectangle &rect) const
Returns true when rectangle contains other rectangle.
void grow(double delta)
Grows the rectangle in place by the specified amount.
double distance(const QgsPointXY &point) const
Returns the distance from point to the nearest point on the boundary of the rectangle.
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
Contains information about a rendered annotation item.
QString itemId() const
Returns the item ID of the associated annotation item.
QString layerId() const
Returns the layer ID of the associated map layer.
QgsRectangle boundingBox() const
Returns the bounding box of the item (in map units).
QList< const QgsRenderedAnnotationItemDetails * > renderedAnnotationItemsInBounds(const QgsRectangle &bounds) const
Returns a list with details of the rendered annotation items within the specified bounds.
QList< QgsRenderedItemDetails * > renderedItems() const
Returns a list of all rendered items.
Responsible for drawing transient features (e.g.
void setIconSize(double iconSize)
Sets the size of the point icons.
void setWidth(double width)
Sets the width of the line.
void setSecondaryStrokeColor(const QColor &color)
Sets a secondary stroke color for the rubberband which will be drawn under the main stroke color.
@ ICON_X
A cross is used to highlight points (x).
@ ICON_FULL_BOX
A full box is used to highlight points (■).
@ ICON_BOX
A box is used to highlight points (□).
void setColor(const QColor &color)
Sets the color for the rubberband.
void setIcon(IconType icon)
Sets the icon type to highlight point geometries.
void addPoint(const QgsPointXY &p, bool doUpdate=true, int geometryIndex=0, int ringIndex=0)
Adds a vertex to the rubberband and update canvas.
Shows a snapping marker on map canvas for the current snapping match.
Represent a 2-dimensional vector.
double y() const
Returns the vector's y-component.
double x() const
Returns the vector's x-component.