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]
47 scaledBounds[2], scaledBounds[3]
64 std::array<float, 4> scaledBounds = scaleBounds( bounds );
66 scaledBounds[0], scaledBounds[1]
69 scaledBounds[2], scaledBounds[3]
83 bool intersects(
const QgsRectangle &bounds,
const std::function<
bool(
int index )> &callback )
const
85 std::array<float, 4> scaledBounds = scaleBounds( bounds );
87 scaledBounds[0], scaledBounds[1]
90 scaledBounds[2], scaledBounds[3]
101 std::array<float, 4> scaleBounds(
const QgsRectangle &bounds )
const
104 static_cast<float>( bounds.
xMinimum() ),
105 static_cast<float>( bounds.
yMinimum() ),
106 static_cast<float>( bounds.
xMaximum() ),
107 static_cast<float>( bounds.
yMaximum() )
134 mLastHoverPoint =
event->originalPixelPoint();
138 const QgsPointXY mapPoint =
event->mapPoint();
145 switch ( mCurrentAction )
147 case Action::NoAction:
149 setHoveredItemFromPoint( mapPoint );
153 case Action::MoveItem:
155 if (
QgsAnnotationItem *item = annotationItemFromId( mSelectedItemLayerId, mSelectedItemId ) )
160 std::unique_ptr<QgsAnnotationItemEditOperationTransientResults> operationResults( item->transientEditResultsV2( &operation, context ) );
161 if ( operationResults )
164 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
165 mTemporaryRubberBand->
setWidth( scaleFactor );
170 mTemporaryRubberBand.
reset();
176 case Action::MoveNode:
178 if (
QgsAnnotationItem *item = annotationItemFromId( mSelectedItemLayerId, mSelectedItemId ) )
182 std::unique_ptr<QgsAnnotationItemEditOperationTransientResults> operationResults( item->transientEditResultsV2( &operation, context ) );
183 if ( operationResults )
186 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
187 mTemporaryRubberBand->
setWidth( scaleFactor );
192 mTemporaryRubberBand.
reset();
208 switch ( mCurrentAction )
210 case Action::NoAction:
212 if ( event->button() != Qt::LeftButton )
215 if ( mHoveredItemId.isEmpty() || !mHoverRubberBand )
219 else if ( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId )
224 const QgsPointXY mapPoint =
event->mapPoint();
229 double currentNodeDistance = std::numeric_limits<double>::max();
230 mHoveredItemNodesSpatialIndex->intersects( searchRect, [&hoveredNode, ¤tNodeDistance, &mapPoint,
this](
int index ) ->
bool {
232 const double nodeDistance = thisNode.
point().sqrDist( mapPoint );
233 if ( nodeDistance < currentNodeDistance )
235 hoveredNode = thisNode;
236 currentNodeDistance = nodeDistance;
241 mMoveStartPointCanvasCrs = mapPoint;
242 mMoveStartPointPixels =
event->pixelPoint();
244 if ( mHoverRubberBand )
245 mHoverRubberBand->hide();
246 if ( mSelectedRubberBand )
247 mSelectedRubberBand->hide();
251 mCurrentAction = Action::MoveItem;
255 mCurrentAction = Action::MoveNode;
256 mTargetNode = hoveredNode;
263 mSelectedItemId = mHoveredItemId;
264 mSelectedItemLayerId = mHoveredItemLayerId;
265 mSelectedItemBounds = mHoveredItemBounds;
267 if ( !mSelectedRubberBand )
268 createSelectedItemBand();
271 mSelectedRubberBand->show();
275 emit
itemSelected( annotationLayerFromId( mSelectedItemLayerId ), mSelectedItemId );
280 case Action::MoveItem:
282 if ( event->button() == Qt::RightButton )
284 mCurrentAction = Action::NoAction;
285 mTemporaryRubberBand.
reset();
286 if ( mSelectedRubberBand )
289 mSelectedRubberBand->show();
291 mHoveredItemNodeRubberBands.clear();
294 else if ( event->button() == Qt::LeftButton )
302 switch (
layer->applyEditV2( &operation, context ) )
306 mRefreshSelectedItemAfterRedraw =
true;
314 mTemporaryRubberBand.
reset();
315 mCurrentAction = Action::NoAction;
321 case Action::MoveNode:
323 if ( event->button() == Qt::RightButton )
325 mCurrentAction = Action::NoAction;
326 mTemporaryRubberBand.
reset();
327 mHoveredItemNodeRubberBands.clear();
328 mTemporaryRubberBand.
reset();
331 else if ( event->button() == Qt::LeftButton )
337 switch (
layer->applyEditV2( &operation, context ) )
341 mRefreshSelectedItemAfterRedraw =
true;
350 mTemporaryRubberBand.
reset();
351 mHoveredItemNodeRubberBands.clear();
352 mHoveredItemNodes.clear();
353 mTemporaryRubberBand.
reset();
354 mCurrentAction = Action::NoAction;
364 switch ( mCurrentAction )
366 case Action::NoAction:
367 case Action::MoveItem:
369 if ( event->button() != Qt::LeftButton )
372 mCurrentAction = Action::NoAction;
373 if ( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId )
384 switch (
layer->applyEditV2( &operation, context ) )
388 mRefreshSelectedItemAfterRedraw =
true;
400 mSelectedItemId = mHoveredItemId;
401 mSelectedItemLayerId = mHoveredItemLayerId;
402 mSelectedItemBounds = mHoveredItemBounds;
404 if ( !mSelectedRubberBand )
405 createSelectedItemBand();
408 mSelectedRubberBand->show();
412 emit
itemSelected( annotationLayerFromId( mSelectedItemLayerId ), mSelectedItemId );
417 case Action::MoveNode:
429 switch ( mCurrentAction )
431 case Action::NoAction:
433 if ( event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete )
435 if ( !
layer || mSelectedItemId.isEmpty() )
438 layer->removeItem( mSelectedItemId );
443 else if ( event->key() == Qt::Key_Left
444 || event->key() == Qt::Key_Right
445 || event->key() == Qt::Key_Up
446 || event->key() == Qt::Key_Down )
454 switch (
layer->applyEditV2( &operation, context ) )
458 mRefreshSelectedItemAfterRedraw =
true;
469 case Action::MoveNode:
471 if ( event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace )
476 switch (
layer->applyEditV2( &operation, context ) )
480 mRefreshSelectedItemAfterRedraw =
true;
490 mTemporaryRubberBand.
reset();
491 mHoveredItemNodeRubberBands.clear();
492 mHoveredItemNodes.clear();
493 mTemporaryRubberBand.
reset();
494 mCurrentAction = Action::NoAction;
502 case Action::MoveItem:
505 if ( event->key() == Qt::Key_Escape )
507 mCurrentAction = Action::NoAction;
508 mTemporaryRubberBand.
reset();
509 if ( mSelectedRubberBand )
512 mSelectedRubberBand->show();
514 mHoveredItemNodeRubberBands.clear();
523void QgsMapToolModifyAnnotation::onCanvasRefreshed()
525 bool needsSelectedItemRefresh = mRefreshSelectedItemAfterRedraw;
526 if (
QgsAnnotationItem *item = annotationItemFromId( mSelectedItemLayerId, mSelectedItemId ) )
530 needsSelectedItemRefresh =
true;
534 if ( needsSelectedItemRefresh )
537 if ( !renderedItemResults )
542 const QList<QgsRenderedItemDetails *> items = renderedItemResults->
renderedItems();
544 if ( const QgsRenderedAnnotationItemDetails *annotationItem = dynamic_cast<const QgsRenderedAnnotationItemDetails *>( item ) )
546 if ( annotationItem->itemId() == mSelectedItemId && annotationItem->layerId() == mSelectedItemLayerId )
551 if ( it != items.end() )
556 if ( !mSelectedRubberBand )
557 createSelectedItemBand();
560 mSelectedRubberBand->show();
561 mSelectedItemBounds = mHoveredItemBounds;
567 const QgsPointXY mapPoint = canvas()->mapSettings().mapToPixel().toMapCoordinates( mLastHoverPoint );
568 setHoveredItemFromPoint( mapPoint );
570 mRefreshSelectedItemAfterRedraw =
false;
575 mHoveredItemNodeRubberBands.clear();
576 if ( mHoveredNodeRubberBand )
577 mHoveredNodeRubberBand->hide();
578 mHoveredItemId = item->
itemId();
579 mHoveredItemLayerId = item->
layerId();
580 mHoveredItemBounds = itemMapBounds;
581 if ( !mHoverRubberBand )
584 mHoverRubberBand->show();
595 if ( !annotationItem )
600 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
606 const QList<QgsAnnotationItemNode> itemNodes = annotationItem->
nodesV2( context );
610 vertexNodeBand->
setWidth( scaleFactor );
612 vertexNodeBand->
setColor( QColor( 200, 0, 120, 255 ) );
615 calloutNodeBand->
setWidth( scaleFactor );
617 calloutNodeBand->
setColor( QColor( 120, 200, 0, 255 ) );
622 mHoveredItemNodesSpatialIndex = std::make_unique<QgsAnnotationItemNodesSpatialIndex>();
624 mHoveredItemNodes.clear();
625 mHoveredItemNodes.reserve( itemNodes.size() );
631 nodeMapPoint = layerToMapTransform.
transform( node.point() );
638 switch ( node.type() )
641 vertexNodeBand->
addPoint( nodeMapPoint );
645 calloutNodeBand->
addPoint( nodeMapPoint );
649 mHoveredItemNodesSpatialIndex->insert( index,
QgsRectangle( nodeMapPoint.
x(), nodeMapPoint.
y(), nodeMapPoint.
x(), nodeMapPoint.
y() ) );
652 transformedNode.
setPoint( nodeMapPoint );
653 mHoveredItemNodes.append( transformedNode );
658 mHoveredItemNodeRubberBands.emplace_back( vertexNodeBand );
659 mHoveredItemNodeRubberBands.emplace_back( calloutNodeBand );
662QSizeF QgsMapToolModifyAnnotation::deltaForKeyEvent(
QgsAnnotationLayer *layer,
const QgsPointXY &originalCanvasPoint, QKeyEvent *event )
664 const double canvasDpi =
canvas()->window()->windowHandle()->screen()->physicalDotsPerInch();
667 double incrementPixels = 0.0;
668 if ( event->modifiers() & Qt::ShiftModifier )
671 incrementPixels = 20.0 / 25.4 * canvasDpi;
673 else if ( event->modifiers() & Qt::AltModifier )
681 incrementPixels = 5.0 / 25.4 * canvasDpi;
684 double deltaXPixels = 0;
685 double deltaYPixels = 0;
686 switch ( event->key() )
689 deltaXPixels = -incrementPixels;
692 deltaXPixels = incrementPixels;
695 deltaYPixels = -incrementPixels;
698 deltaYPixels = incrementPixels;
707 const QgsPointXY afterMoveCanvasPoint( originalCanvasPoint.
x() + deltaXPixels, originalCanvasPoint.
y() + deltaYPixels );
711 return QSizeF( afterMoveLayerPoint.
x() - beforeMoveLayerPoint.
x(), afterMoveLayerPoint.
y() - beforeMoveLayerPoint.
y() );
717 double closestItemDistance = std::numeric_limits<double>::max();
718 double closestItemArea = std::numeric_limits<double>::max();
723 if ( !annotationItem )
727 const double itemDistance = itemBounds.
contains( mapPoint ) ? 0 : itemBounds.
distance( mapPoint );
728 if ( !closestItem || itemDistance < closestItemDistance || ( itemDistance == closestItemDistance && itemBounds.
area() < closestItemArea ) )
731 closestItemDistance = itemDistance;
732 closestItemArea = itemBounds.
area();
739QgsAnnotationLayer *QgsMapToolModifyAnnotation::annotationLayerFromId(
const QString &layerId )
747QgsAnnotationItem *QgsMapToolModifyAnnotation::annotationItemFromId(
const QString &layerId,
const QString &itemId )
750 return layer ?
layer->item( itemId ) :
nullptr;
753void QgsMapToolModifyAnnotation::setHoveredItemFromPoint(
const QgsPointXY &mapPoint )
759 if ( !renderedItemResults )
781 if ( closestItem->
itemId() != mHoveredItemId || closestItem->
layerId() != mHoveredItemLayerId )
783 setHoveredItem( closestItem, itemBounds );
788 if ( closestItem->
itemId() == mSelectedItemId && closestItem->
layerId() == mSelectedItemLayerId )
790 double currentNodeDistance = std::numeric_limits<double>::max();
791 mHoveredItemNodesSpatialIndex->intersects( searchRect, [&hoveredNode, ¤tNodeDistance, &mapPoint,
this](
int index ) ->
bool {
792 if ( index >= mHoveredItemNodes.size() )
796 const double nodeDistance = thisNode.
point().
sqrDist( mapPoint );
797 if ( nodeDistance < currentNodeDistance )
799 hoveredNode = thisNode;
800 currentNodeDistance = nodeDistance;
809 if ( mHoveredNodeRubberBand )
810 mHoveredNodeRubberBand->hide();
811 setCursor( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId ? Qt::OpenHandCursor : Qt::ArrowCursor );
815 if ( !mHoveredNodeRubberBand )
816 createHoveredNodeBand();
820 mHoveredNodeRubberBand->show();
826void QgsMapToolModifyAnnotation::clearHoveredItem()
828 if ( mHoverRubberBand )
829 mHoverRubberBand->hide();
830 if ( mHoveredNodeRubberBand )
831 mHoveredNodeRubberBand->hide();
833 mHoveredItemId.clear();
834 mHoveredItemLayerId.clear();
835 mHoveredItemNodeRubberBands.clear();
836 mHoveredItemNodesSpatialIndex.reset();
841void QgsMapToolModifyAnnotation::clearSelectedItem()
843 if ( mSelectedRubberBand )
844 mSelectedRubberBand->hide();
846 const bool hadSelection = !mSelectedItemId.isEmpty();
847 mSelectedItemId.clear();
848 mSelectedItemLayerId.clear();
853void QgsMapToolModifyAnnotation::createHoverBand()
855 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
858 mHoverRubberBand->
setWidth( scaleFactor );
860 mHoverRubberBand->
setColor( QColor( 100, 100, 100, 155 ) );
863void QgsMapToolModifyAnnotation::createHoveredNodeBand()
865 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
869 mHoveredNodeRubberBand->
setWidth( scaleFactor );
870 mHoveredNodeRubberBand->
setIconSize( scaleFactor * 5 );
871 mHoveredNodeRubberBand->
setColor( QColor( 200, 0, 120, 255 ) );
874void QgsMapToolModifyAnnotation::createSelectedItemBand()
876 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
879 mSelectedRubberBand->
setWidth( scaleFactor );
881 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.