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 );
46 scaledBounds[0], scaledBounds[1]
49 scaledBounds[2], scaledBounds[3]
64 void remove(
int index,
const QgsRectangle &bounds )
66 std::array<float, 4> scaledBounds = scaleBounds( bounds );
68 scaledBounds[0], scaledBounds[1]
71 scaledBounds[2], scaledBounds[3]
85 bool intersects(
const QgsRectangle &bounds,
const std::function<
bool(
int index )> &callback )
const
87 std::array<float, 4> scaledBounds = scaleBounds( bounds );
89 scaledBounds[0], scaledBounds[1]
92 scaledBounds[2], scaledBounds[3]
103 std::array<float, 4> scaleBounds(
const QgsRectangle &bounds )
const
106 static_cast<float>( bounds.
xMinimum() ),
107 static_cast<float>( bounds.
yMinimum() ),
108 static_cast<float>( bounds.
xMaximum() ),
109 static_cast<float>( bounds.
yMaximum() )
136 mLastHoverPoint =
event->originalPixelPoint();
140 const QgsPointXY mapPoint =
event->mapPoint();
147 switch ( mCurrentAction )
149 case Action::NoAction:
151 setHoveredItemFromPoint( mapPoint );
155 case Action::MoveItem:
162 std::unique_ptr<QgsAnnotationItemEditOperationTransientResults> operationResults( item->transientEditResultsV2( &operation, context ) );
163 if ( operationResults )
165 mTemporaryRubberBand.reset(
new QgsRubberBand(
mCanvas, operationResults->representativeGeometry().type() ) );
166 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
167 mTemporaryRubberBand->setWidth( scaleFactor );
168 mTemporaryRubberBand->setToGeometry( operationResults->representativeGeometry(),
layer->crs() );
172 mTemporaryRubberBand.reset();
178 case Action::MoveNode:
184 std::unique_ptr<QgsAnnotationItemEditOperationTransientResults> operationResults( item->transientEditResultsV2( &operation, context ) );
185 if ( operationResults )
187 mTemporaryRubberBand.reset(
new QgsRubberBand(
mCanvas, operationResults->representativeGeometry().type() ) );
188 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
189 mTemporaryRubberBand->setWidth( scaleFactor );
190 mTemporaryRubberBand->setToGeometry( operationResults->representativeGeometry(),
layer->crs() );
194 mTemporaryRubberBand.reset();
210 switch ( mCurrentAction )
212 case Action::NoAction:
214 if ( event->button() != Qt::LeftButton )
217 if ( mHoveredItemId.isEmpty() || !mHoverRubberBand )
221 else if ( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId )
226 const QgsPointXY mapPoint =
event->mapPoint();
231 double currentNodeDistance = std::numeric_limits<double>::max();
232 mHoveredItemNodesSpatialIndex->intersects( searchRect, [&hoveredNode, ¤tNodeDistance, &mapPoint,
this](
int index ) ->
bool {
234 const double nodeDistance = thisNode.
point().
sqrDist( mapPoint );
235 if ( nodeDistance < currentNodeDistance )
237 hoveredNode = thisNode;
238 currentNodeDistance = nodeDistance;
243 mMoveStartPointCanvasCrs = mapPoint;
244 mMoveStartPointPixels =
event->pixelPoint();
246 if ( mHoverRubberBand )
247 mHoverRubberBand->hide();
248 if ( mSelectedRubberBand )
249 mSelectedRubberBand->hide();
253 mCurrentAction = Action::MoveItem;
257 mCurrentAction = Action::MoveNode;
258 mTargetNode = hoveredNode;
265 mSelectedItemId = mHoveredItemId;
266 mSelectedItemLayerId = mHoveredItemLayerId;
267 mSelectedItemBounds = mHoveredItemBounds;
269 if ( !mSelectedRubberBand )
270 createSelectedItemBand();
272 mSelectedRubberBand->copyPointsFrom( mHoverRubberBand );
273 mSelectedRubberBand->show();
282 case Action::MoveItem:
284 if ( event->button() == Qt::RightButton )
286 mCurrentAction = Action::NoAction;
287 mTemporaryRubberBand.reset();
288 if ( mSelectedRubberBand )
290 mSelectedRubberBand->setTranslationOffset( 0, 0 );
291 mSelectedRubberBand->show();
293 mHoveredItemNodeRubberBands.clear();
296 else if ( event->button() == Qt::LeftButton )
304 switch (
layer->applyEditV2( &operation, context ) )
308 mRefreshSelectedItemAfterRedraw =
true;
316 mTemporaryRubberBand.reset();
317 mCurrentAction = Action::NoAction;
323 case Action::MoveNode:
325 if ( event->button() == Qt::RightButton )
327 mCurrentAction = Action::NoAction;
328 mTemporaryRubberBand.reset();
329 mHoveredItemNodeRubberBands.clear();
330 mTemporaryRubberBand.reset();
333 else if ( event->button() == Qt::LeftButton )
339 switch (
layer->applyEditV2( &operation, context ) )
343 mRefreshSelectedItemAfterRedraw =
true;
352 mTemporaryRubberBand.reset();
353 mHoveredItemNodeRubberBands.clear();
354 mHoveredItemNodes.clear();
355 mTemporaryRubberBand.reset();
356 mCurrentAction = Action::NoAction;
366 switch ( mCurrentAction )
368 case Action::NoAction:
369 case Action::MoveItem:
371 if ( event->button() != Qt::LeftButton )
374 mCurrentAction = Action::NoAction;
375 if ( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId )
386 switch (
layer->applyEditV2( &operation, context ) )
390 mRefreshSelectedItemAfterRedraw =
true;
402 mSelectedItemId = mHoveredItemId;
403 mSelectedItemLayerId = mHoveredItemLayerId;
404 mSelectedItemBounds = mHoveredItemBounds;
406 if ( !mSelectedRubberBand )
407 createSelectedItemBand();
409 mSelectedRubberBand->copyPointsFrom( mHoverRubberBand );
410 mSelectedRubberBand->show();
419 case Action::MoveNode:
431 switch ( mCurrentAction )
433 case Action::NoAction:
435 if ( event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete )
437 if ( !
layer || mSelectedItemId.isEmpty() )
440 layer->removeItem( mSelectedItemId );
445 else if ( event->key() == Qt::Key_Left
446 || event->key() == Qt::Key_Right
447 || event->key() == Qt::Key_Up
448 || event->key() == Qt::Key_Down )
453 const QSizeF deltaLayerCoordinates = deltaForKeyEvent(
layer, mSelectedRubberBand->asGeometry().centroid().asPoint(), event );
456 switch (
layer->applyEditV2( &operation, context ) )
460 mRefreshSelectedItemAfterRedraw =
true;
471 case Action::MoveNode:
473 if ( event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace )
478 switch (
layer->applyEditV2( &operation, context ) )
482 mRefreshSelectedItemAfterRedraw =
true;
492 mTemporaryRubberBand.reset();
493 mHoveredItemNodeRubberBands.clear();
494 mHoveredItemNodes.clear();
495 mTemporaryRubberBand.reset();
496 mCurrentAction = Action::NoAction;
504 case Action::MoveItem:
507 if ( event->key() == Qt::Key_Escape )
509 mCurrentAction = Action::NoAction;
510 mTemporaryRubberBand.reset();
511 if ( mSelectedRubberBand )
513 mSelectedRubberBand->setTranslationOffset( 0, 0 );
514 mSelectedRubberBand->show();
516 mHoveredItemNodeRubberBands.clear();
525void QgsMapToolModifyAnnotation::onCanvasRefreshed()
527 bool needsSelectedItemRefresh = mRefreshSelectedItemAfterRedraw;
532 needsSelectedItemRefresh =
true;
536 if ( needsSelectedItemRefresh )
539 if ( !renderedItemResults )
544 const QList<QgsRenderedItemDetails *> items = renderedItemResults->
renderedItems();
545 auto it = std::find_if( items.begin(), items.end(), [
this](
const QgsRenderedItemDetails *item ) {
546 if ( const QgsRenderedAnnotationItemDetails *annotationItem = dynamic_cast<const QgsRenderedAnnotationItemDetails *>( item ) )
548 if ( annotationItem->itemId() == mSelectedItemId && annotationItem->layerId() == mSelectedItemLayerId )
553 if ( it != items.end() )
555 const QgsRectangle itemBounds = ( *it )->boundingBox();
557 setHoveredItem(
dynamic_cast<const QgsRenderedAnnotationItemDetails *
>( *it ), itemBounds );
558 if ( !mSelectedRubberBand )
559 createSelectedItemBand();
561 mSelectedRubberBand->copyPointsFrom( mHoverRubberBand );
562 mSelectedRubberBand->show();
563 mSelectedItemBounds = mHoveredItemBounds;
569 const QgsPointXY mapPoint = canvas()->mapSettings().mapToPixel().toMapCoordinates( mLastHoverPoint );
570 setHoveredItemFromPoint( mapPoint );
572 mRefreshSelectedItemAfterRedraw =
false;
577 mHoveredItemNodeRubberBands.clear();
578 if ( mHoveredNodeRubberBand )
579 mHoveredNodeRubberBand->hide();
580 mHoveredItemId = item->
itemId();
581 mHoveredItemLayerId = item->
layerId();
582 mHoveredItemBounds = itemMapBounds;
583 if ( !mHoverRubberBand )
586 mHoverRubberBand->show();
589 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.
xMinimum(), itemMapBounds.
yMinimum() ) );
590 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.
xMaximum(), itemMapBounds.
yMinimum() ) );
591 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.
xMaximum(), itemMapBounds.
yMaximum() ) );
592 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.
xMinimum(), itemMapBounds.
yMaximum() ) );
593 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.
xMinimum(), itemMapBounds.
yMinimum() ) );
597 if ( !annotationItem )
600 QgsCoordinateTransform layerToMapTransform = QgsCoordinateTransform(
layer->crs(),
canvas()->mapSettings().destinationCrs(),
canvas()->mapSettings().transformContext() );
602 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
604 QgsAnnotationItemEditContext context;
608 const QList<QgsAnnotationItemNode> itemNodes = annotationItem->
nodesV2( context );
612 vertexNodeBand->
setWidth( scaleFactor );
614 vertexNodeBand->
setColor( QColor( 200, 0, 120, 255 ) );
617 calloutNodeBand->
setWidth( scaleFactor );
619 calloutNodeBand->
setColor( QColor( 120, 200, 0, 255 ) );
624 mHoveredItemNodesSpatialIndex = std::make_unique<QgsAnnotationItemNodesSpatialIndex>();
626 mHoveredItemNodes.clear();
627 mHoveredItemNodes.reserve( itemNodes.size() );
628 for (
const QgsAnnotationItemNode &node : itemNodes )
630 QgsPointXY nodeMapPoint;
633 nodeMapPoint = layerToMapTransform.
transform( node.point() );
635 catch ( QgsCsException & )
640 switch ( node.type() )
643 vertexNodeBand->
addPoint( nodeMapPoint );
647 calloutNodeBand->
addPoint( nodeMapPoint );
651 mHoveredItemNodesSpatialIndex->insert( index, QgsRectangle( nodeMapPoint.
x(), nodeMapPoint.
y(), nodeMapPoint.
x(), nodeMapPoint.
y() ) );
653 QgsAnnotationItemNode transformedNode = node;
654 transformedNode.
setPoint( nodeMapPoint );
655 mHoveredItemNodes.append( transformedNode );
660 mHoveredItemNodeRubberBands.emplace_back( vertexNodeBand );
661 mHoveredItemNodeRubberBands.emplace_back( calloutNodeBand );
664QSizeF QgsMapToolModifyAnnotation::deltaForKeyEvent(
QgsAnnotationLayer *layer,
const QgsPointXY &originalCanvasPoint, QKeyEvent *event )
666 const double canvasDpi =
canvas()->window()->windowHandle()->screen()->physicalDotsPerInch();
669 double incrementPixels = 0.0;
670 if ( event->modifiers() & Qt::ShiftModifier )
673 incrementPixels = 20.0 / 25.4 * canvasDpi;
675 else if ( event->modifiers() & Qt::AltModifier )
683 incrementPixels = 5.0 / 25.4 * canvasDpi;
686 double deltaXPixels = 0;
687 double deltaYPixels = 0;
688 switch ( event->key() )
691 deltaXPixels = -incrementPixels;
694 deltaXPixels = incrementPixels;
697 deltaYPixels = -incrementPixels;
700 deltaYPixels = incrementPixels;
709 const QgsPointXY afterMoveCanvasPoint( originalCanvasPoint.
x() + deltaXPixels, originalCanvasPoint.
y() + deltaYPixels );
713 return QSizeF( afterMoveLayerPoint.
x() - beforeMoveLayerPoint.
x(), afterMoveLayerPoint.
y() - beforeMoveLayerPoint.
y() );
716void QgsMapToolModifyAnnotation::setHoveredItemFromPoint(
const QgsPointXY &mapPoint )
718 QgsRectangle searchRect = QgsRectangle( mapPoint.
x(), mapPoint.
y(), mapPoint.
x(), mapPoint.
y() );
722 if ( !renderedItemResults )
736 QgsRectangle itemBounds;
737 const QgsRenderedAnnotationItemDetails *closestItem =
findClosestItemToPoint( mapPoint, items, itemBounds );
744 if ( closestItem->
itemId() != mHoveredItemId || closestItem->
layerId() != mHoveredItemLayerId )
746 setHoveredItem( closestItem, itemBounds );
750 QgsAnnotationItemNode hoveredNode;
751 if ( closestItem->
itemId() == mSelectedItemId && closestItem->
layerId() == mSelectedItemLayerId )
753 double currentNodeDistance = std::numeric_limits<double>::max();
754 mHoveredItemNodesSpatialIndex->intersects( searchRect, [&hoveredNode, ¤tNodeDistance, &mapPoint,
this](
int index ) ->
bool {
755 if ( index >= mHoveredItemNodes.size() )
758 const QgsAnnotationItemNode &thisNode = mHoveredItemNodes.at( index );
759 const double nodeDistance = thisNode.
point().
sqrDist( mapPoint );
760 if ( nodeDistance < currentNodeDistance )
762 hoveredNode = thisNode;
763 currentNodeDistance = nodeDistance;
772 if ( mHoveredNodeRubberBand )
773 mHoveredNodeRubberBand->hide();
774 setCursor( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId ? Qt::OpenHandCursor : Qt::ArrowCursor );
778 if ( !mHoveredNodeRubberBand )
779 createHoveredNodeBand();
782 mHoveredNodeRubberBand->addPoint( hoveredNode.
point() );
783 mHoveredNodeRubberBand->show();
789void QgsMapToolModifyAnnotation::clearHoveredItem()
791 if ( mHoverRubberBand )
792 mHoverRubberBand->hide();
793 if ( mHoveredNodeRubberBand )
794 mHoveredNodeRubberBand->hide();
796 mHoveredItemId.clear();
797 mHoveredItemLayerId.clear();
798 mHoveredItemNodeRubberBands.clear();
799 mHoveredItemNodesSpatialIndex.reset();
804void QgsMapToolModifyAnnotation::clearSelectedItem()
806 if ( mSelectedRubberBand )
807 mSelectedRubberBand->hide();
809 const bool hadSelection = !mSelectedItemId.isEmpty();
810 mSelectedItemId.clear();
811 mSelectedItemLayerId.clear();
816void QgsMapToolModifyAnnotation::createHoverBand()
818 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
821 mHoverRubberBand->setWidth( scaleFactor );
822 mHoverRubberBand->setSecondaryStrokeColor( QColor( 255, 255, 255, 100 ) );
823 mHoverRubberBand->setColor( QColor( 100, 100, 100, 155 ) );
826void QgsMapToolModifyAnnotation::createHoveredNodeBand()
828 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
832 mHoveredNodeRubberBand->setWidth( scaleFactor );
833 mHoveredNodeRubberBand->setIconSize( scaleFactor * 5 );
834 mHoveredNodeRubberBand->setColor( QColor( 200, 0, 120, 255 ) );
837void QgsMapToolModifyAnnotation::createSelectedItemBand()
839 const double scaleFactor =
canvas()->fontMetrics().xHeight() * .2;
842 mSelectedRubberBand->setWidth( scaleFactor );
843 mSelectedRubberBand->setSecondaryStrokeColor( QColor( 255, 255, 255, 100 ) );
844 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.