36#include "moc_qgsmaptoolmodifyannotation.cpp"
39class QgsAnnotationItemNodesSpatialIndex :
public RTree<int, float, 2, float>
42 void insert(
int index,
const QgsRectangle &bounds )
44 std::array<float, 4> scaledBounds = scaleBounds( bounds );
45 const float aMin[2] { scaledBounds[0], scaledBounds[1] };
46 const float aMax[2] { scaledBounds[2], scaledBounds[3] };
47 this->Insert( aMin, aMax, index );
56 void remove(
int index,
const QgsRectangle &bounds )
58 std::array<float, 4> scaledBounds = scaleBounds( bounds );
59 const float aMin[2] { scaledBounds[0], scaledBounds[1] };
60 const float aMax[2] { scaledBounds[2], scaledBounds[3] };
61 this->Remove( aMin, aMax, index );
69 bool intersects(
const QgsRectangle &bounds,
const std::function<
bool(
int index )> &callback )
const
71 std::array<float, 4> scaledBounds = scaleBounds( bounds );
72 const float aMin[2] { scaledBounds[0], scaledBounds[1] };
73 const float aMax[2] { scaledBounds[2], scaledBounds[3] };
74 this->Search( aMin, aMax, callback );
79 std::array<float, 4> scaleBounds(
const QgsRectangle &bounds )
const
81 return {
static_cast<float>( bounds.
xMinimum() ),
static_cast<float>( bounds.
yMinimum() ),
static_cast<float>( bounds.
xMaximum() ),
static_cast<float>( bounds.
yMaximum() ) };
107 mLastHoverPoint =
event->originalPixelPoint();
111 const QgsPointXY mapPoint =
event->mapPoint();
118 switch ( mCurrentAction )
120 case Action::NoAction:
122 setHoveredItemFromPoint( mapPoint );
126 case Action::MoveItem:
133 operation( mSelectedItemId, delta.
x(), delta.
y(), event->
pixelPoint().x() - mMoveStartPointPixels.x(), event->
pixelPoint().y() - mMoveStartPointPixels.y() );
134 std::unique_ptr<QgsAnnotationItemEditOperationTransientResults> operationResults( item->transientEditResultsV2( &operation, context ) );
135 if ( operationResults )
137 mTemporaryRubberBand.reset(
new QgsRubberBand(
mCanvas, operationResults->representativeGeometry().type() ) );
138 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
139 mTemporaryRubberBand->setWidth( scaleFactor );
140 mTemporaryRubberBand->setToGeometry( operationResults->representativeGeometry(),
layer->crs() );
144 mTemporaryRubberBand.reset();
150 case Action::MoveNode:
160 event->
pixelPoint().x() - mMoveStartPointPixels.x(),
161 event->
pixelPoint().y() - mMoveStartPointPixels.y()
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();
189 switch ( mCurrentAction )
191 case Action::NoAction:
193 if ( event->button() != Qt::LeftButton )
196 if ( mHoveredItemId.isEmpty() || !mHoverRubberBand )
200 else if ( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId )
205 const QgsPointXY mapPoint =
event->mapPoint();
210 double currentNodeDistance = std::numeric_limits<double>::max();
211 mHoveredItemNodesSpatialIndex->intersects( searchRect, [&hoveredNode, ¤tNodeDistance, &mapPoint,
this](
int index ) ->
bool {
213 const double nodeDistance = thisNode.
point().
sqrDist( mapPoint );
214 if ( nodeDistance < currentNodeDistance )
216 hoveredNode = thisNode;
217 currentNodeDistance = nodeDistance;
222 mMoveStartPointCanvasCrs = mapPoint;
223 mMoveStartPointPixels =
event->pixelPoint();
225 if ( mHoverRubberBand )
226 mHoverRubberBand->hide();
227 if ( mSelectedRubberBand )
228 mSelectedRubberBand->hide();
232 mCurrentAction = Action::MoveItem;
236 mCurrentAction = Action::MoveNode;
237 mTargetNode = hoveredNode;
244 mSelectedItemId = mHoveredItemId;
245 mSelectedItemLayerId = mHoveredItemLayerId;
246 mSelectedItemBounds = mHoveredItemBounds;
248 if ( !mSelectedRubberBand )
249 createSelectedItemBand();
251 mSelectedRubberBand->copyPointsFrom( mHoverRubberBand );
252 mSelectedRubberBand->show();
261 case Action::MoveItem:
263 if ( event->button() == Qt::RightButton )
265 mCurrentAction = Action::NoAction;
266 mTemporaryRubberBand.reset();
267 if ( mSelectedRubberBand )
269 mSelectedRubberBand->setTranslationOffset( 0, 0 );
270 mSelectedRubberBand->show();
272 mHoveredItemNodeRubberBands.clear();
275 else if ( event->button() == Qt::LeftButton )
283 operation( mSelectedItemId, delta.
x(), delta.
y(), event->
pixelPoint().x() - mMoveStartPointPixels.x(), event->
pixelPoint().y() - mMoveStartPointPixels.y() );
284 switch (
layer->applyEditV2( &operation, context ) )
288 mRefreshSelectedItemAfterRedraw =
true;
296 mTemporaryRubberBand.reset();
297 mCurrentAction = Action::NoAction;
303 case Action::MoveNode:
305 if ( event->button() == Qt::RightButton )
307 mCurrentAction = Action::NoAction;
308 mTemporaryRubberBand.reset();
309 mHoveredItemNodeRubberBands.clear();
310 mTemporaryRubberBand.reset();
313 else if ( event->button() == Qt::LeftButton )
323 event->
pixelPoint().x() - mMoveStartPointPixels.x(),
324 event->
pixelPoint().y() - mMoveStartPointPixels.y()
326 switch (
layer->applyEditV2( &operation, context ) )
330 mRefreshSelectedItemAfterRedraw =
true;
339 mTemporaryRubberBand.reset();
340 mHoveredItemNodeRubberBands.clear();
341 mHoveredItemNodes.clear();
342 mTemporaryRubberBand.reset();
343 mCurrentAction = Action::NoAction;
353 switch ( mCurrentAction )
355 case Action::NoAction:
356 case Action::MoveItem:
358 if ( event->button() != Qt::LeftButton )
361 mCurrentAction = Action::NoAction;
362 if ( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId )
373 switch (
layer->applyEditV2( &operation, context ) )
377 mRefreshSelectedItemAfterRedraw =
true;
389 mSelectedItemId = mHoveredItemId;
390 mSelectedItemLayerId = mHoveredItemLayerId;
391 mSelectedItemBounds = mHoveredItemBounds;
393 if ( !mSelectedRubberBand )
394 createSelectedItemBand();
396 mSelectedRubberBand->copyPointsFrom( mHoverRubberBand );
397 mSelectedRubberBand->show();
406 case Action::MoveNode:
418 switch ( mCurrentAction )
420 case Action::NoAction:
422 if ( event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete )
424 if ( !
layer || mSelectedItemId.isEmpty() )
427 layer->removeItem( mSelectedItemId );
432 else if ( event->key() == Qt::Key_Left || event->key() == Qt::Key_Right || event->key() == Qt::Key_Up || event->key() == Qt::Key_Down )
437 const QSizeF deltaLayerCoordinates = deltaForKeyEvent(
layer, mSelectedRubberBand->asGeometry().centroid().asPoint(), event );
440 switch (
layer->applyEditV2( &operation, context ) )
444 mRefreshSelectedItemAfterRedraw =
true;
455 case Action::MoveNode:
457 if ( event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace )
462 switch (
layer->applyEditV2( &operation, context ) )
466 mRefreshSelectedItemAfterRedraw =
true;
476 mTemporaryRubberBand.reset();
477 mHoveredItemNodeRubberBands.clear();
478 mHoveredItemNodes.clear();
479 mTemporaryRubberBand.reset();
480 mCurrentAction = Action::NoAction;
488 case Action::MoveItem:
491 if ( event->key() == Qt::Key_Escape )
493 mCurrentAction = Action::NoAction;
494 mTemporaryRubberBand.reset();
495 if ( mSelectedRubberBand )
497 mSelectedRubberBand->setTranslationOffset( 0, 0 );
498 mSelectedRubberBand->show();
500 mHoveredItemNodeRubberBands.clear();
509void QgsMapToolModifyAnnotation::onCanvasRefreshed()
511 bool needsSelectedItemRefresh = mRefreshSelectedItemAfterRedraw;
516 needsSelectedItemRefresh =
true;
520 if ( needsSelectedItemRefresh )
523 if ( !renderedItemResults )
528 const QList<QgsRenderedItemDetails *> items = renderedItemResults->
renderedItems();
529 auto it = std::find_if( items.begin(), items.end(), [
this](
const QgsRenderedItemDetails *item ) {
530 if ( const QgsRenderedAnnotationItemDetails *annotationItem = dynamic_cast<const QgsRenderedAnnotationItemDetails *>( item ) )
532 if ( annotationItem->itemId() == mSelectedItemId && annotationItem->layerId() == mSelectedItemLayerId )
537 if ( it != items.end() )
539 const QgsRectangle itemBounds = ( *it )->boundingBox();
541 setHoveredItem(
dynamic_cast<const QgsRenderedAnnotationItemDetails *
>( *it ), itemBounds );
542 if ( !mSelectedRubberBand )
543 createSelectedItemBand();
545 mSelectedRubberBand->copyPointsFrom( mHoverRubberBand );
546 mSelectedRubberBand->show();
547 mSelectedItemBounds = mHoveredItemBounds;
553 const QgsPointXY mapPoint = canvas()->mapSettings().mapToPixel().toMapCoordinates( mLastHoverPoint );
554 setHoveredItemFromPoint( mapPoint );
556 mRefreshSelectedItemAfterRedraw =
false;
561 mHoveredItemNodeRubberBands.clear();
562 if ( mHoveredNodeRubberBand )
563 mHoveredNodeRubberBand->hide();
564 mHoveredItemId = item->
itemId();
565 mHoveredItemLayerId = item->
layerId();
566 mHoveredItemBounds = itemMapBounds;
567 if ( !mHoverRubberBand )
570 mHoverRubberBand->show();
573 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.
xMinimum(), itemMapBounds.
yMinimum() ) );
574 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.
xMaximum(), itemMapBounds.
yMinimum() ) );
575 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.
xMaximum(), itemMapBounds.
yMaximum() ) );
576 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.
xMinimum(), itemMapBounds.
yMaximum() ) );
577 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.
xMinimum(), itemMapBounds.
yMinimum() ) );
581 if ( !annotationItem )
584 QgsCoordinateTransform layerToMapTransform = QgsCoordinateTransform(
layer->crs(),
canvas()->mapSettings().destinationCrs(),
canvas()->mapSettings().transformContext() );
586 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
588 QgsAnnotationItemEditContext context;
592 const QList<QgsAnnotationItemNode> itemNodes = annotationItem->
nodesV2( context );
596 vertexNodeBand->
setWidth( scaleFactor );
598 vertexNodeBand->
setColor( QColor( 200, 0, 120, 255 ) );
601 calloutNodeBand->
setWidth( scaleFactor );
603 calloutNodeBand->
setColor( QColor( 120, 200, 0, 255 ) );
608 mHoveredItemNodesSpatialIndex = std::make_unique<QgsAnnotationItemNodesSpatialIndex>();
610 mHoveredItemNodes.clear();
611 mHoveredItemNodes.reserve( itemNodes.size() );
612 for (
const QgsAnnotationItemNode &node : itemNodes )
614 QgsPointXY nodeMapPoint;
617 nodeMapPoint = layerToMapTransform.
transform( node.point() );
619 catch ( QgsCsException & )
624 switch ( node.type() )
627 vertexNodeBand->
addPoint( nodeMapPoint );
631 calloutNodeBand->
addPoint( nodeMapPoint );
635 mHoveredItemNodesSpatialIndex->insert( index, QgsRectangle( nodeMapPoint.
x(), nodeMapPoint.
y(), nodeMapPoint.
x(), nodeMapPoint.
y() ) );
637 QgsAnnotationItemNode transformedNode = node;
638 transformedNode.
setPoint( nodeMapPoint );
639 mHoveredItemNodes.append( transformedNode );
644 mHoveredItemNodeRubberBands.emplace_back( vertexNodeBand );
645 mHoveredItemNodeRubberBands.emplace_back( calloutNodeBand );
648QSizeF QgsMapToolModifyAnnotation::deltaForKeyEvent(
QgsAnnotationLayer *layer,
const QgsPointXY &originalCanvasPoint, QKeyEvent *event )
650 const double canvasDpi =
canvas()->window()->windowHandle()->screen()->physicalDotsPerInch();
653 double incrementPixels = 0.0;
654 if ( event->modifiers() & Qt::ShiftModifier )
657 incrementPixels = 20.0 / 25.4 * canvasDpi;
659 else if ( event->modifiers() & Qt::AltModifier )
667 incrementPixels = 5.0 / 25.4 * canvasDpi;
670 double deltaXPixels = 0;
671 double deltaYPixels = 0;
672 switch ( event->key() )
675 deltaXPixels = -incrementPixels;
678 deltaXPixels = incrementPixels;
681 deltaYPixels = -incrementPixels;
684 deltaYPixels = incrementPixels;
693 const QgsPointXY afterMoveCanvasPoint( originalCanvasPoint.
x() + deltaXPixels, originalCanvasPoint.
y() + deltaYPixels );
697 return QSizeF( afterMoveLayerPoint.
x() - beforeMoveLayerPoint.
x(), afterMoveLayerPoint.
y() - beforeMoveLayerPoint.
y() );
700void QgsMapToolModifyAnnotation::setHoveredItemFromPoint(
const QgsPointXY &mapPoint )
702 QgsRectangle searchRect = QgsRectangle( mapPoint.
x(), mapPoint.
y(), mapPoint.
x(), mapPoint.
y() );
706 if ( !renderedItemResults )
720 QgsRectangle itemBounds;
721 const QgsRenderedAnnotationItemDetails *closestItem =
findClosestItemToPoint( mapPoint, items, itemBounds );
728 if ( closestItem->
itemId() != mHoveredItemId || closestItem->
layerId() != mHoveredItemLayerId )
730 setHoveredItem( closestItem, itemBounds );
734 QgsAnnotationItemNode hoveredNode;
735 if ( closestItem->
itemId() == mSelectedItemId && closestItem->
layerId() == mSelectedItemLayerId )
737 double currentNodeDistance = std::numeric_limits<double>::max();
738 mHoveredItemNodesSpatialIndex->intersects( searchRect, [&hoveredNode, ¤tNodeDistance, &mapPoint,
this](
int index ) ->
bool {
739 if ( index >= mHoveredItemNodes.size() )
742 const QgsAnnotationItemNode &thisNode = mHoveredItemNodes.at( index );
743 const double nodeDistance = thisNode.
point().
sqrDist( mapPoint );
744 if ( nodeDistance < currentNodeDistance )
746 hoveredNode = thisNode;
747 currentNodeDistance = nodeDistance;
756 if ( mHoveredNodeRubberBand )
757 mHoveredNodeRubberBand->hide();
758 setCursor( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId ? Qt::OpenHandCursor : Qt::ArrowCursor );
762 if ( !mHoveredNodeRubberBand )
763 createHoveredNodeBand();
766 mHoveredNodeRubberBand->addPoint( hoveredNode.
point() );
767 mHoveredNodeRubberBand->show();
773void QgsMapToolModifyAnnotation::clearHoveredItem()
775 if ( mHoverRubberBand )
776 mHoverRubberBand->hide();
777 if ( mHoveredNodeRubberBand )
778 mHoveredNodeRubberBand->hide();
780 mHoveredItemId.clear();
781 mHoveredItemLayerId.clear();
782 mHoveredItemNodeRubberBands.clear();
783 mHoveredItemNodesSpatialIndex.reset();
788void QgsMapToolModifyAnnotation::clearSelectedItem()
790 if ( mSelectedRubberBand )
791 mSelectedRubberBand->hide();
793 const bool hadSelection = !mSelectedItemId.isEmpty();
794 mSelectedItemId.clear();
795 mSelectedItemLayerId.clear();
800void QgsMapToolModifyAnnotation::createHoverBand()
802 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
805 mHoverRubberBand->setWidth( scaleFactor );
806 mHoverRubberBand->setSecondaryStrokeColor( QColor( 255, 255, 255, 100 ) );
807 mHoverRubberBand->setColor( QColor( 100, 100, 100, 155 ) );
810void QgsMapToolModifyAnnotation::createHoveredNodeBand()
812 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
816 mHoveredNodeRubberBand->setWidth( scaleFactor );
817 mHoveredNodeRubberBand->setIconSize( scaleFactor * 5 );
818 mHoveredNodeRubberBand->setColor( QColor( 200, 0, 120, 255 ) );
821void QgsMapToolModifyAnnotation::createSelectedItemBand()
823 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
826 mSelectedRubberBand->setWidth( scaleFactor );
827 mSelectedRubberBand->setSecondaryStrokeColor( QColor( 255, 255, 255, 100 ) );
828 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.
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.
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.
void setDirty(bool b=true)
Flag the project as dirty (modified).
A rectangle specified with double values.
void grow(double delta)
Grows the rectangle in place by the specified amount.
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.
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.