18#include "moc_qgsmaptoolmodifyannotation.cpp"
37class QgsAnnotationItemNodesSpatialIndex :
public RTree<int, float, 2, float>
42 std::array<float, 4> scaledBounds = scaleBounds( bounds );
44 { scaledBounds[0], scaledBounds[1]
46 { scaledBounds[2], scaledBounds[3]
60 std::array<float, 4> scaledBounds = scaleBounds( bounds );
62 { scaledBounds[0], scaledBounds[1]
64 { scaledBounds[2], scaledBounds[3]
75 bool intersects(
const QgsRectangle &bounds,
const std::function<
bool(
int index )> &callback )
const
77 std::array<float, 4> scaledBounds = scaleBounds( bounds );
79 { scaledBounds[0], scaledBounds[1]
81 { scaledBounds[2], scaledBounds[3]
89 std::array<float, 4> scaleBounds(
const QgsRectangle &bounds )
const
92 static_cast<float>( bounds.
xMinimum() ),
93 static_cast<float>( bounds.
yMinimum() ),
94 static_cast<float>( bounds.
xMaximum() ),
95 static_cast<float>( bounds.
yMaximum() )
122 mLastHoverPoint =
event->originalPixelPoint();
126 const QgsPointXY mapPoint =
event->mapPoint();
133 switch ( mCurrentAction )
135 case Action::NoAction:
137 setHoveredItemFromPoint( mapPoint );
141 case Action::MoveItem:
143 if (
QgsAnnotationItem *item = annotationItemFromId( mSelectedItemLayerId, mSelectedItemId ) )
148 std::unique_ptr<QgsAnnotationItemEditOperationTransientResults> operationResults( item->transientEditResultsV2( &operation, context ) );
149 if ( operationResults )
152 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
153 mTemporaryRubberBand->
setWidth( scaleFactor );
158 mTemporaryRubberBand.
reset();
164 case Action::MoveNode:
166 if (
QgsAnnotationItem *item = annotationItemFromId( mSelectedItemLayerId, mSelectedItemId ) )
170 std::unique_ptr<QgsAnnotationItemEditOperationTransientResults> operationResults( item->transientEditResultsV2( &operation, context ) );
171 if ( operationResults )
174 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
175 mTemporaryRubberBand->
setWidth( scaleFactor );
180 mTemporaryRubberBand.
reset();
196 switch ( mCurrentAction )
198 case Action::NoAction:
200 if ( event->button() != Qt::LeftButton )
203 if ( mHoveredItemId.isEmpty() || !mHoverRubberBand )
207 else if ( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId )
212 const QgsPointXY mapPoint =
event->mapPoint();
217 double currentNodeDistance = std::numeric_limits<double>::max();
218 mHoveredItemNodesSpatialIndex->intersects( searchRect, [&hoveredNode, ¤tNodeDistance, &mapPoint,
this](
int index ) ->
bool {
220 const double nodeDistance = thisNode.
point().sqrDist( mapPoint );
221 if ( nodeDistance < currentNodeDistance )
223 hoveredNode = thisNode;
224 currentNodeDistance = nodeDistance;
229 mMoveStartPointCanvasCrs = mapPoint;
230 mMoveStartPointPixels =
event->pixelPoint();
232 if ( mHoverRubberBand )
233 mHoverRubberBand->hide();
234 if ( mSelectedRubberBand )
235 mSelectedRubberBand->hide();
239 mCurrentAction = Action::MoveItem;
243 mCurrentAction = Action::MoveNode;
244 mTargetNode = hoveredNode;
251 mSelectedItemId = mHoveredItemId;
252 mSelectedItemLayerId = mHoveredItemLayerId;
253 mSelectedItemBounds = mHoveredItemBounds;
255 if ( !mSelectedRubberBand )
256 createSelectedItemBand();
259 mSelectedRubberBand->show();
263 emit
itemSelected( annotationLayerFromId( mSelectedItemLayerId ), mSelectedItemId );
268 case Action::MoveItem:
270 if ( event->button() == Qt::RightButton )
272 mCurrentAction = Action::NoAction;
273 mTemporaryRubberBand.
reset();
274 if ( mSelectedRubberBand )
277 mSelectedRubberBand->show();
279 mHoveredItemNodeRubberBands.clear();
282 else if ( event->button() == Qt::LeftButton )
290 switch (
layer->applyEditV2( &operation, context ) )
294 mRefreshSelectedItemAfterRedraw =
true;
302 mTemporaryRubberBand.
reset();
303 mCurrentAction = Action::NoAction;
309 case Action::MoveNode:
311 if ( event->button() == Qt::RightButton )
313 mCurrentAction = Action::NoAction;
314 mTemporaryRubberBand.
reset();
315 mHoveredItemNodeRubberBands.clear();
316 mTemporaryRubberBand.
reset();
319 else if ( event->button() == Qt::LeftButton )
325 switch (
layer->applyEditV2( &operation, context ) )
329 mRefreshSelectedItemAfterRedraw =
true;
338 mTemporaryRubberBand.
reset();
339 mHoveredItemNodeRubberBands.clear();
340 mHoveredItemNodes.clear();
341 mTemporaryRubberBand.
reset();
342 mCurrentAction = Action::NoAction;
352 switch ( mCurrentAction )
354 case Action::NoAction:
355 case Action::MoveItem:
357 if ( event->button() != Qt::LeftButton )
360 mCurrentAction = Action::NoAction;
361 if ( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId )
372 switch (
layer->applyEditV2( &operation, context ) )
376 mRefreshSelectedItemAfterRedraw =
true;
388 mSelectedItemId = mHoveredItemId;
389 mSelectedItemLayerId = mHoveredItemLayerId;
390 mSelectedItemBounds = mHoveredItemBounds;
392 if ( !mSelectedRubberBand )
393 createSelectedItemBand();
396 mSelectedRubberBand->show();
400 emit
itemSelected( annotationLayerFromId( mSelectedItemLayerId ), mSelectedItemId );
405 case Action::MoveNode:
417 switch ( mCurrentAction )
419 case Action::NoAction:
421 if ( event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete )
423 if ( !
layer || mSelectedItemId.isEmpty() )
426 layer->removeItem( mSelectedItemId );
431 else if ( event->key() == Qt::Key_Left
432 || event->key() == Qt::Key_Right
433 || event->key() == Qt::Key_Up
434 || event->key() == Qt::Key_Down )
442 switch (
layer->applyEditV2( &operation, context ) )
446 mRefreshSelectedItemAfterRedraw =
true;
457 case Action::MoveNode:
459 if ( event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace )
464 switch (
layer->applyEditV2( &operation, context ) )
468 mRefreshSelectedItemAfterRedraw =
true;
478 mTemporaryRubberBand.
reset();
479 mHoveredItemNodeRubberBands.clear();
480 mHoveredItemNodes.clear();
481 mTemporaryRubberBand.
reset();
482 mCurrentAction = Action::NoAction;
490 case Action::MoveItem:
493 if ( event->key() == Qt::Key_Escape )
495 mCurrentAction = Action::NoAction;
496 mTemporaryRubberBand.
reset();
497 if ( mSelectedRubberBand )
500 mSelectedRubberBand->show();
502 mHoveredItemNodeRubberBands.clear();
511void QgsMapToolModifyAnnotation::onCanvasRefreshed()
513 bool needsSelectedItemRefresh = mRefreshSelectedItemAfterRedraw;
514 if (
QgsAnnotationItem *item = annotationItemFromId( mSelectedItemLayerId, mSelectedItemId ) )
518 needsSelectedItemRefresh =
true;
522 if ( needsSelectedItemRefresh )
525 if ( !renderedItemResults )
530 const QList<QgsRenderedItemDetails *> items = renderedItemResults->
renderedItems();
532 if ( const QgsRenderedAnnotationItemDetails *annotationItem = dynamic_cast<const QgsRenderedAnnotationItemDetails *>( item ) )
534 if ( annotationItem->itemId() == mSelectedItemId && annotationItem->layerId() == mSelectedItemLayerId )
539 if ( it != items.end() )
544 if ( !mSelectedRubberBand )
545 createSelectedItemBand();
548 mSelectedRubberBand->show();
549 mSelectedItemBounds = mHoveredItemBounds;
555 const QgsPointXY mapPoint = canvas()->mapSettings().mapToPixel().toMapCoordinates( mLastHoverPoint );
556 setHoveredItemFromPoint( mapPoint );
558 mRefreshSelectedItemAfterRedraw =
false;
563 mHoveredItemNodeRubberBands.clear();
564 if ( mHoveredNodeRubberBand )
565 mHoveredNodeRubberBand->hide();
566 mHoveredItemId = item->
itemId();
567 mHoveredItemLayerId = item->
layerId();
568 mHoveredItemBounds = itemMapBounds;
569 if ( !mHoverRubberBand )
572 mHoverRubberBand->show();
583 if ( !annotationItem )
588 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
594 const QList<QgsAnnotationItemNode> itemNodes = annotationItem->
nodesV2( context );
598 vertexNodeBand->
setWidth( scaleFactor );
600 vertexNodeBand->
setColor( QColor( 200, 0, 120, 255 ) );
603 calloutNodeBand->
setWidth( scaleFactor );
605 calloutNodeBand->
setColor( QColor( 120, 200, 0, 255 ) );
610 mHoveredItemNodesSpatialIndex = std::make_unique<QgsAnnotationItemNodesSpatialIndex>();
612 mHoveredItemNodes.clear();
613 mHoveredItemNodes.reserve( itemNodes.size() );
619 nodeMapPoint = layerToMapTransform.
transform( node.point() );
626 switch ( node.type() )
629 vertexNodeBand->
addPoint( nodeMapPoint );
633 calloutNodeBand->
addPoint( nodeMapPoint );
637 mHoveredItemNodesSpatialIndex->insert( index,
QgsRectangle( nodeMapPoint.
x(), nodeMapPoint.
y(), nodeMapPoint.
x(), nodeMapPoint.
y() ) );
640 transformedNode.
setPoint( nodeMapPoint );
641 mHoveredItemNodes.append( transformedNode );
646 mHoveredItemNodeRubberBands.emplace_back( vertexNodeBand );
647 mHoveredItemNodeRubberBands.emplace_back( calloutNodeBand );
650QSizeF QgsMapToolModifyAnnotation::deltaForKeyEvent(
QgsAnnotationLayer *layer,
const QgsPointXY &originalCanvasPoint, QKeyEvent *event )
652 const double canvasDpi =
canvas()->window()->windowHandle()->screen()->physicalDotsPerInch();
655 double incrementPixels = 0.0;
656 if ( event->modifiers() & Qt::ShiftModifier )
659 incrementPixels = 20.0 / 25.4 * canvasDpi;
661 else if ( event->modifiers() & Qt::AltModifier )
669 incrementPixels = 5.0 / 25.4 * canvasDpi;
672 double deltaXPixels = 0;
673 double deltaYPixels = 0;
674 switch ( event->key() )
677 deltaXPixels = -incrementPixels;
680 deltaXPixels = incrementPixels;
683 deltaYPixels = -incrementPixels;
686 deltaYPixels = incrementPixels;
695 const QgsPointXY afterMoveCanvasPoint( originalCanvasPoint.
x() + deltaXPixels, originalCanvasPoint.
y() + deltaYPixels );
699 return QSizeF( afterMoveLayerPoint.
x() - beforeMoveLayerPoint.
x(), afterMoveLayerPoint.
y() - beforeMoveLayerPoint.
y() );
705 double closestItemDistance = std::numeric_limits<double>::max();
706 double closestItemArea = std::numeric_limits<double>::max();
711 if ( !annotationItem )
715 const double itemDistance = itemBounds.
contains( mapPoint ) ? 0 : itemBounds.
distance( mapPoint );
716 if ( !closestItem || itemDistance < closestItemDistance || ( itemDistance == closestItemDistance && itemBounds.
area() < closestItemArea ) )
719 closestItemDistance = itemDistance;
720 closestItemArea = itemBounds.
area();
727QgsAnnotationLayer *QgsMapToolModifyAnnotation::annotationLayerFromId(
const QString &layerId )
735QgsAnnotationItem *QgsMapToolModifyAnnotation::annotationItemFromId(
const QString &layerId,
const QString &itemId )
738 return layer ?
layer->item( itemId ) :
nullptr;
741void QgsMapToolModifyAnnotation::setHoveredItemFromPoint(
const QgsPointXY &mapPoint )
747 if ( !renderedItemResults )
769 if ( closestItem->
itemId() != mHoveredItemId || closestItem->
layerId() != mHoveredItemLayerId )
771 setHoveredItem( closestItem, itemBounds );
776 if ( closestItem->
itemId() == mSelectedItemId && closestItem->
layerId() == mSelectedItemLayerId )
778 double currentNodeDistance = std::numeric_limits<double>::max();
779 mHoveredItemNodesSpatialIndex->intersects( searchRect, [&hoveredNode, ¤tNodeDistance, &mapPoint,
this](
int index ) ->
bool {
780 if ( index >= mHoveredItemNodes.size() )
784 const double nodeDistance = thisNode.
point().
sqrDist( mapPoint );
785 if ( nodeDistance < currentNodeDistance )
787 hoveredNode = thisNode;
788 currentNodeDistance = nodeDistance;
797 if ( mHoveredNodeRubberBand )
798 mHoveredNodeRubberBand->hide();
799 setCursor( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId ? Qt::OpenHandCursor : Qt::ArrowCursor );
803 if ( !mHoveredNodeRubberBand )
804 createHoveredNodeBand();
808 mHoveredNodeRubberBand->show();
814void QgsMapToolModifyAnnotation::clearHoveredItem()
816 if ( mHoverRubberBand )
817 mHoverRubberBand->hide();
818 if ( mHoveredNodeRubberBand )
819 mHoveredNodeRubberBand->hide();
821 mHoveredItemId.clear();
822 mHoveredItemLayerId.clear();
823 mHoveredItemNodeRubberBands.clear();
824 mHoveredItemNodesSpatialIndex.reset();
829void QgsMapToolModifyAnnotation::clearSelectedItem()
831 if ( mSelectedRubberBand )
832 mSelectedRubberBand->hide();
834 const bool hadSelection = !mSelectedItemId.isEmpty();
835 mSelectedItemId.clear();
836 mSelectedItemLayerId.clear();
841void QgsMapToolModifyAnnotation::createHoverBand()
843 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
846 mHoverRubberBand->
setWidth( scaleFactor );
848 mHoverRubberBand->
setColor( QColor( 100, 100, 100, 155 ) );
851void QgsMapToolModifyAnnotation::createHoveredNodeBand()
853 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
857 mHoveredNodeRubberBand->
setWidth( scaleFactor );
858 mHoveredNodeRubberBand->
setIconSize( scaleFactor * 5 );
859 mHoveredNodeRubberBand->
setColor( QColor( 200, 0, 120, 255 ) );
862void QgsMapToolModifyAnnotation::createSelectedItemBand()
864 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
867 mSelectedRubberBand->
setWidth( scaleFactor );
869 mSelectedRubberBand->
setColor( QColor( 50, 50, 50, 200 ) );
void reset(T *p=nullptr)
Will reset the managed pointer to p.
@ 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.
QgsVertexId id() const
Returns the ID number of the node, used for uniquely identifying the node in the item.
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.
Custom exception class for Coordinate Reference System related exceptions.
QgsPointXY asPoint() const
Returns the contents of the geometry as a 2-dimensional point.
QgsGeometry centroid() const
Returns the center of mass of a geometry.
Map canvas is a class for displaying all GIS data types on a canvas.
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.
QgsCoordinateReferenceSystem crs
A QgsMapMouseEvent is the result of a user interaction with the mouse on 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.
A class to represent a 2D point.
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.
Base class for detailed information about a rendered 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).
Stores collated details of rendered items during a map rendering operation.
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.
A class for drawing transient features (e.g.
void setIconSize(double iconSize)
Sets the size of the point icons.
QgsGeometry asGeometry() const
Returns the rubberband as a Geometry.
void setWidth(double width)
Sets the width of the line.
void reset(Qgis::GeometryType geometryType=Qgis::GeometryType::Line)
Clears all the geometries in this rubberband.
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 setToGeometry(const QgsGeometry &geom, QgsVectorLayer *layer)
Sets this rubber band to geom.
void setIcon(IconType icon)
Sets the icon type to highlight point geometries.
void copyPointsFrom(const QgsRubberBand *other)
Copies the points from another rubber band.
void setTranslationOffset(double dx, double dy)
Adds translation to original coordinates (all in map coordinates)
void addPoint(const QgsPointXY &p, bool doUpdate=true, int geometryIndex=0, int ringIndex=0)
Adds a vertex to the rubberband and update canvas.
Class that shows snapping marker on map canvas for the current snapping match.
A class to represent a vector.
double y() const
Returns the vector's y-component.
double x() const
Returns the vector's x-component.