18#include "moc_qgsmaptoolmodifyannotation.cpp"
37class QgsAnnotationItemNodesSpatialIndex :
public RTree<int, float, 2, float>
43 std::array< float, 4 > scaledBounds = scaleBounds( bounds );
46 scaledBounds[0], scaledBounds[ 1]
49 scaledBounds[2], scaledBounds[3]
62 std::array< float, 4 > scaledBounds = scaleBounds( bounds );
65 scaledBounds[0], scaledBounds[ 1]
68 scaledBounds[2], scaledBounds[3]
78 bool intersects(
const QgsRectangle &bounds,
const std::function<
bool(
int index )> &callback )
const
80 std::array< float, 4 > scaledBounds = scaleBounds( bounds );
83 scaledBounds[0], scaledBounds[ 1]
86 scaledBounds[2], scaledBounds[3]
93 std::array<float, 4> scaleBounds(
const QgsRectangle &bounds )
const
97 static_cast< float >( bounds.
xMinimum() ),
98 static_cast< float >( bounds.
yMinimum() ),
99 static_cast< float >( bounds.
xMaximum() ),
100 static_cast< float >( bounds.
yMaximum() )
128 mLastHoverPoint =
event->originalPixelPoint();
132 const QgsPointXY mapPoint =
event->mapPoint();
139 switch ( mCurrentAction )
141 case Action::NoAction:
143 setHoveredItemFromPoint( mapPoint );
147 case Action::MoveItem:
149 if (
QgsAnnotationItem *item = annotationItemFromId( mSelectedItemLayerId, mSelectedItemId ) )
154 event->
pixelPoint().x() - mMoveStartPointPixels.
x(),
155 event->
pixelPoint().y() - mMoveStartPointPixels.
y() );
156 std::unique_ptr< QgsAnnotationItemEditOperationTransientResults > operationResults( item->transientEditResultsV2( &operation, context ) );
157 if ( operationResults )
160 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
161 mTemporaryRubberBand->
setWidth( scaleFactor );
166 mTemporaryRubberBand.
reset();
172 case Action::MoveNode:
174 if (
QgsAnnotationItem *item = annotationItemFromId( mSelectedItemLayerId, mSelectedItemId ) )
178 event->
pixelPoint().x() - mMoveStartPointPixels.
x(),
179 event->
pixelPoint().y() - mMoveStartPointPixels.
y() );
180 std::unique_ptr< QgsAnnotationItemEditOperationTransientResults > operationResults( item->transientEditResultsV2( &operation, context ) );
181 if ( operationResults )
184 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
185 mTemporaryRubberBand->
setWidth( scaleFactor );
190 mTemporaryRubberBand.
reset();
206 switch ( mCurrentAction )
208 case Action::NoAction:
210 if ( event->button() != Qt::LeftButton )
213 if ( mHoveredItemId.isEmpty() || !mHoverRubberBand )
217 else if ( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId )
222 const QgsPointXY mapPoint =
event->mapPoint();
227 double currentNodeDistance = std::numeric_limits< double >::max();
228 mHoveredItemNodesSpatialIndex->intersects( searchRect, [&hoveredNode, ¤tNodeDistance, &mapPoint,
this](
int index )->
bool
231 const double nodeDistance = thisNode.
point().sqrDist( mapPoint );
232 if ( nodeDistance < currentNodeDistance )
234 hoveredNode = thisNode;
235 currentNodeDistance = nodeDistance;
240 mMoveStartPointCanvasCrs = mapPoint;
241 mMoveStartPointPixels =
event->pixelPoint();
243 if ( mHoverRubberBand )
244 mHoverRubberBand->hide();
245 if ( mSelectedRubberBand )
246 mSelectedRubberBand->hide();
250 mCurrentAction = Action::MoveItem;
254 mCurrentAction = Action::MoveNode;
255 mTargetNode = hoveredNode;
262 mSelectedItemId = mHoveredItemId;
263 mSelectedItemLayerId = mHoveredItemLayerId;
264 mSelectedItemBounds = mHoveredItemBounds;
266 if ( !mSelectedRubberBand )
267 createSelectedItemBand();
270 mSelectedRubberBand->show();
274 emit
itemSelected( annotationLayerFromId( mSelectedItemLayerId ), mSelectedItemId );
279 case Action::MoveItem:
281 if ( event->button() == Qt::RightButton )
283 mCurrentAction = Action::NoAction;
284 mTemporaryRubberBand.
reset();
285 if ( mSelectedRubberBand )
288 mSelectedRubberBand->show();
290 mHoveredItemNodeRubberBands.clear();
293 else if ( event->button() == Qt::LeftButton )
301 event->
pixelPoint().x() - mMoveStartPointPixels.
x(),
302 event->
pixelPoint().y() - mMoveStartPointPixels.
y() );
303 switch (
layer->applyEditV2( &operation, context ) )
307 mRefreshSelectedItemAfterRedraw =
true;
315 mTemporaryRubberBand.
reset();
316 mCurrentAction = Action::NoAction;
322 case Action::MoveNode:
324 if ( event->button() == Qt::RightButton )
326 mCurrentAction = Action::NoAction;
327 mTemporaryRubberBand.
reset();
328 mHoveredItemNodeRubberBands.clear();
329 mTemporaryRubberBand.
reset();
332 else if ( event->button() == Qt::LeftButton )
338 event->
pixelPoint().x() - mMoveStartPointPixels.
x(),
339 event->
pixelPoint().y() - mMoveStartPointPixels.
y() );
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();
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 )
439 if ( !
layer || mSelectedItemId.isEmpty() )
442 layer->removeItem( mSelectedItemId );
447 else if ( event->key() == Qt::Key_Left
448 || event->key() == Qt::Key_Right
449 || event->key() == Qt::Key_Up
450 || event->key() == Qt::Key_Down )
458 switch (
layer->applyEditV2( &operation, context ) )
462 mRefreshSelectedItemAfterRedraw =
true;
473 case Action::MoveNode:
475 if ( event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace )
480 switch (
layer->applyEditV2( &operation, context ) )
484 mRefreshSelectedItemAfterRedraw =
true;
494 mTemporaryRubberBand.
reset();
495 mHoveredItemNodeRubberBands.clear();
496 mHoveredItemNodes.clear();
497 mTemporaryRubberBand.
reset();
498 mCurrentAction = Action::NoAction;
506 case Action::MoveItem:
509 if ( event->key() == Qt::Key_Escape )
511 mCurrentAction = Action::NoAction;
512 mTemporaryRubberBand.
reset();
513 if ( mSelectedRubberBand )
516 mSelectedRubberBand->show();
518 mHoveredItemNodeRubberBands.clear();
527void QgsMapToolModifyAnnotation::onCanvasRefreshed()
529 bool needsSelectedItemRefresh = mRefreshSelectedItemAfterRedraw;
530 if (
QgsAnnotationItem *item = annotationItemFromId( mSelectedItemLayerId, mSelectedItemId ) )
534 needsSelectedItemRefresh =
true;
538 if ( needsSelectedItemRefresh )
541 if ( !renderedItemResults )
546 const QList<QgsRenderedItemDetails *> items = renderedItemResults->
renderedItems();
549 if ( const QgsRenderedAnnotationItemDetails *annotationItem = dynamic_cast< const QgsRenderedAnnotationItemDetails *>( item ) )
551 if ( annotationItem->itemId() == mSelectedItemId && annotationItem->layerId() == mSelectedItemLayerId )
556 if ( it != items.end() )
561 if ( !mSelectedRubberBand )
562 createSelectedItemBand();
565 mSelectedRubberBand->show();
566 mSelectedItemBounds = mHoveredItemBounds;
572 const QgsPointXY mapPoint = canvas()->mapSettings().mapToPixel().toMapCoordinates( mLastHoverPoint );
573 setHoveredItemFromPoint( mapPoint );
575 mRefreshSelectedItemAfterRedraw =
false;
580 mHoveredItemNodeRubberBands.clear();
581 if ( mHoveredNodeRubberBand )
582 mHoveredNodeRubberBand->hide();
583 mHoveredItemId = item->
itemId();
584 mHoveredItemLayerId = item->
layerId();
585 mHoveredItemBounds = itemMapBounds;
586 if ( !mHoverRubberBand )
589 mHoverRubberBand->show();
600 if ( !annotationItem )
605 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
611 const QList< QgsAnnotationItemNode > itemNodes = annotationItem->
nodesV2( context );
615 vertexNodeBand->
setWidth( scaleFactor );
617 vertexNodeBand->
setColor( QColor( 200, 0, 120, 255 ) );
620 calloutNodeBand->
setWidth( scaleFactor );
622 calloutNodeBand->
setColor( QColor( 120, 200, 0, 255 ) );
627 mHoveredItemNodesSpatialIndex = std::make_unique< QgsAnnotationItemNodesSpatialIndex >();
629 mHoveredItemNodes.clear();
630 mHoveredItemNodes.reserve( itemNodes.size() );
636 nodeMapPoint = layerToMapTransform.
transform( node.point() );
643 switch ( node.type() )
646 vertexNodeBand->
addPoint( nodeMapPoint );
650 calloutNodeBand->
addPoint( nodeMapPoint );
654 mHoveredItemNodesSpatialIndex->insert( index,
QgsRectangle( nodeMapPoint.
x(), nodeMapPoint.
y(),
655 nodeMapPoint.
x(), nodeMapPoint.
y() ) );
658 transformedNode.
setPoint( nodeMapPoint );
659 mHoveredItemNodes.append( transformedNode );
664 mHoveredItemNodeRubberBands.emplace_back( vertexNodeBand );
665 mHoveredItemNodeRubberBands.emplace_back( calloutNodeBand );
668QSizeF QgsMapToolModifyAnnotation::deltaForKeyEvent(
QgsAnnotationLayer *layer,
const QgsPointXY &originalCanvasPoint, QKeyEvent *event )
670 const double canvasDpi =
canvas()->window()->windowHandle()->screen()->physicalDotsPerInch();
673 double incrementPixels = 0.0;
674 if ( event->modifiers() & Qt::ShiftModifier )
677 incrementPixels = 20.0 / 25.4 * canvasDpi;
679 else if ( event->modifiers() & Qt::AltModifier )
687 incrementPixels = 5.0 / 25.4 * canvasDpi;
690 double deltaXPixels = 0;
691 double deltaYPixels = 0;
692 switch ( event->key() )
695 deltaXPixels = -incrementPixels;
698 deltaXPixels = incrementPixels;
701 deltaYPixels = -incrementPixels;
704 deltaYPixels = incrementPixels;
713 const QgsPointXY afterMoveCanvasPoint( originalCanvasPoint.
x() + deltaXPixels, originalCanvasPoint.
y() + deltaYPixels );
717 return QSizeF( afterMoveLayerPoint.
x() - beforeMoveLayerPoint.
x(), afterMoveLayerPoint.
y() - beforeMoveLayerPoint.
y() );
723 double closestItemDistance = std::numeric_limits< double >::max();
724 double closestItemArea = std::numeric_limits< double >::max();
729 if ( !annotationItem )
733 const double itemDistance = itemBounds.
contains( mapPoint ) ? 0 : itemBounds.
distance( mapPoint );
734 if ( !closestItem || itemDistance < closestItemDistance || ( itemDistance == closestItemDistance && itemBounds.
area() < closestItemArea ) )
737 closestItemDistance = itemDistance;
738 closestItemArea = itemBounds.
area();
745QgsAnnotationLayer *QgsMapToolModifyAnnotation::annotationLayerFromId(
const QString &layerId )
753QgsAnnotationItem *QgsMapToolModifyAnnotation::annotationItemFromId(
const QString &layerId,
const QString &itemId )
756 return layer ?
layer->item( itemId ) :
nullptr;
759void QgsMapToolModifyAnnotation::setHoveredItemFromPoint(
const QgsPointXY &mapPoint )
765 if ( !renderedItemResults )
787 if ( closestItem->
itemId() != mHoveredItemId || closestItem->
layerId() != mHoveredItemLayerId )
789 setHoveredItem( closestItem, itemBounds );
794 if ( closestItem->
itemId() == mSelectedItemId && closestItem->
layerId() == mSelectedItemLayerId )
796 double currentNodeDistance = std::numeric_limits< double >::max();
797 mHoveredItemNodesSpatialIndex->intersects( searchRect, [&hoveredNode, ¤tNodeDistance, &mapPoint,
this](
int index )->
bool
799 if ( index >= mHoveredItemNodes.size() )
803 const double nodeDistance = thisNode.
point().
sqrDist( mapPoint );
804 if ( nodeDistance < currentNodeDistance )
806 hoveredNode = thisNode;
807 currentNodeDistance = nodeDistance;
816 if ( mHoveredNodeRubberBand )
817 mHoveredNodeRubberBand->hide();
818 setCursor( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId ? Qt::OpenHandCursor : Qt::ArrowCursor );
822 if ( !mHoveredNodeRubberBand )
823 createHoveredNodeBand();
827 mHoveredNodeRubberBand->show();
833void QgsMapToolModifyAnnotation::clearHoveredItem()
835 if ( mHoverRubberBand )
836 mHoverRubberBand->hide();
837 if ( mHoveredNodeRubberBand )
838 mHoveredNodeRubberBand->hide();
840 mHoveredItemId.clear();
841 mHoveredItemLayerId.clear();
842 mHoveredItemNodeRubberBands.clear();
843 mHoveredItemNodesSpatialIndex.reset();
848void QgsMapToolModifyAnnotation::clearSelectedItem()
850 if ( mSelectedRubberBand )
851 mSelectedRubberBand->hide();
853 const bool hadSelection = !mSelectedItemId.isEmpty();
854 mSelectedItemId.clear();
855 mSelectedItemLayerId.clear();
860void QgsMapToolModifyAnnotation::createHoverBand()
862 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
865 mHoverRubberBand->
setWidth( scaleFactor );
867 mHoverRubberBand->
setColor( QColor( 100, 100, 100, 155 ) );
870void QgsMapToolModifyAnnotation::createHoveredNodeBand()
872 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
876 mHoveredNodeRubberBand->
setWidth( scaleFactor );
877 mHoveredNodeRubberBand->
setIconSize( scaleFactor * 5 );
878 mHoveredNodeRubberBand->
setColor( QColor( 200, 0, 120, 255 ) );
881void QgsMapToolModifyAnnotation::createSelectedItemBand()
883 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
886 mSelectedRubberBand->
setWidth( scaleFactor );
888 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.
double area() const
Returns the area of the rectangle.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
double xMaximum() const
Returns the x maximum value (right side of rectangle).
double yMaximum() const
Returns the y maximum value (top side of 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.