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 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.
 
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.
 
Responsible 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.
 
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.