QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
qgsmaptoolmodifyannotation.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsmaptoolmodifyannotation.cpp
3  ----------------
4  copyright : (C) 2021 by Nyall Dawson
5  email : nyall dot dawson at gmail dot com
6  ***************************************************************************/
7 
8 /***************************************************************************
9  * *
10  * This program is free software; you can redistribute it and/or modify *
11  * it under the terms of the GNU General Public License as published by *
12  * the Free Software Foundation; either version 2 of the License, or *
13  * (at your option) any later version. *
14  * *
15  ***************************************************************************/
16 
18 #include "qgsrubberband.h"
19 #include "qgsmapmouseevent.h"
20 #include "qgsmapcanvas.h"
21 #include "qgsrendereditemresults.h"
22 #include "qgsrendereditemdetails.h"
23 #include "qgsannotationlayer.h"
24 #include "qgsproject.h"
26 #include "qgsannotationitem.h"
27 #include "qgsannotationitemnode.h"
29 #include "qgssnapindicator.h"
30 #include "RTree.h"
31 #include <QTransform>
32 #include <QWindow>
33 #include <QScreen>
34 
36 class QgsAnnotationItemNodesSpatialIndex : public RTree<int, float, 2, float>
37 {
38  public:
39 
40  void insert( int index, const QgsRectangle &bounds )
41  {
42  std::array< float, 4 > scaledBounds = scaleBounds( bounds );
43  this->Insert(
44  {
45  scaledBounds[0], scaledBounds[ 1]
46  },
47  {
48  scaledBounds[2], scaledBounds[3]
49  },
50  index );
51  }
52 
59  void remove( int index, const QgsRectangle &bounds )
60  {
61  std::array< float, 4 > scaledBounds = scaleBounds( bounds );
62  this->Remove(
63  {
64  scaledBounds[0], scaledBounds[ 1]
65  },
66  {
67  scaledBounds[2], scaledBounds[3]
68  },
69  index );
70  }
71 
77  bool intersects( const QgsRectangle &bounds, const std::function< bool( int index )> &callback ) const
78  {
79  std::array< float, 4 > scaledBounds = scaleBounds( bounds );
80  this->Search(
81  {
82  scaledBounds[0], scaledBounds[ 1]
83  },
84  {
85  scaledBounds[2], scaledBounds[3]
86  },
87  callback );
88  return true;
89  }
90 
91  private:
92  std::array<float, 4> scaleBounds( const QgsRectangle &bounds ) const
93  {
94  return
95  {
96  static_cast< float >( bounds.xMinimum() ),
97  static_cast< float >( bounds.yMinimum() ),
98  static_cast< float >( bounds.xMaximum() ),
99  static_cast< float >( bounds.yMaximum() )
100  };
101  }
102 };
104 
105 
107  : QgsMapToolAdvancedDigitizing( canvas, cadDockWidget )
108  , mSnapIndicator( new QgsSnapIndicator( canvas ) )
109 {
110 
111  connect( QgsMapToolModifyAnnotation::canvas(), &QgsMapCanvas::mapCanvasRefreshed, this, &QgsMapToolModifyAnnotation::onCanvasRefreshed );
112 }
113 
115 
117 {
118  mSnapIndicator->setMatch( QgsPointLocator::Match() );
119 
120  clearHoveredItem();
122 }
123 
125 {
126  event->snapPoint();
127  mSnapIndicator->setMatch( event->mapPointMatch() );
128 
129  const QgsPointXY mapPoint = event->mapPoint();
130 
131  switch ( mCurrentAction )
132  {
133  case Action::NoAction:
134  {
135  QgsRectangle searchRect = QgsRectangle( mapPoint.x(), mapPoint.y(), mapPoint.x(), mapPoint.y() );
136  searchRect.grow( searchRadiusMU( canvas() ) );
137 
138  const QgsRenderedItemResults *renderedItemResults = canvas()->renderedItemResults( false );
139  if ( !renderedItemResults )
140  {
141  clearHoveredItem();
142  return;
143  }
144 
145  const QList<const QgsRenderedAnnotationItemDetails *> items = renderedItemResults->renderedAnnotationItemsInBounds( searchRect );
146  if ( items.empty() )
147  {
148  clearHoveredItem();
149  return;
150  }
151 
152  // find closest item
153  QgsRectangle itemBounds;
154  const QgsRenderedAnnotationItemDetails *closestItem = findClosestItemToPoint( mapPoint, items, itemBounds );
155  if ( !closestItem )
156  {
157  clearHoveredItem();
158  return;
159  }
160 
161  if ( closestItem->itemId() != mHoveredItemId || closestItem->layerId() != mHoveredItemLayerId )
162  {
163  setHoveredItem( closestItem, itemBounds );
164  }
165 
166  // track hovered node too!... here we want to identify the closest node to the cursor position
167  QgsAnnotationItemNode hoveredNode;
168  if ( closestItem->itemId() == mSelectedItemId && closestItem->layerId() == mSelectedItemLayerId )
169  {
170  double currentNodeDistance = std::numeric_limits< double >::max();
171  mHoveredItemNodesSpatialIndex->intersects( searchRect, [&hoveredNode, &currentNodeDistance, &mapPoint, this]( int index )-> bool
172  {
173  const QgsAnnotationItemNode &thisNode = mHoveredItemNodes.at( index );
174  const double nodeDistance = thisNode.point().sqrDist( mapPoint );
175  if ( nodeDistance < currentNodeDistance )
176  {
177  hoveredNode = thisNode;
178  currentNodeDistance = nodeDistance;
179  }
180  return true;
181  } );
182  }
183 
184  if ( hoveredNode.point().isEmpty() )
185  {
186  // no hovered node
187  if ( mHoveredNodeRubberBand )
188  mHoveredNodeRubberBand->hide();
189  setCursor( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId ? Qt::OpenHandCursor : Qt::ArrowCursor );
190  }
191  else
192  {
193  if ( !mHoveredNodeRubberBand )
194  createHoveredNodeBand();
195 
196  mHoveredNodeRubberBand->reset( QgsWkbTypes::PointGeometry );
197  mHoveredNodeRubberBand->addPoint( hoveredNode.point() );
198  mHoveredNodeRubberBand->show();
199 
200  setCursor( Qt::ArrowCursor );
201  }
202  break;
203  }
204 
205  case Action::MoveItem:
206  {
207  if ( QgsAnnotationItem *item = annotationItemFromId( mSelectedItemLayerId, mSelectedItemId ) )
208  {
209  QgsAnnotationLayer *layer = annotationLayerFromId( mSelectedItemLayerId );
210  const QgsVector delta = toLayerCoordinates( layer, event->mapPoint() ) - mMoveStartPointLayerCrs;
211 
212  QgsAnnotationItemEditOperationTranslateItem operation( mSelectedItemId, delta.x(), delta.y() );
213  std::unique_ptr< QgsAnnotationItemEditOperationTransientResults > operationResults( item->transientEditResults( &operation ) );
214  if ( operationResults )
215  {
216  mTemporaryRubberBand.reset( new QgsRubberBand( mCanvas, operationResults->representativeGeometry().type() ) );
217  const double scaleFactor = canvas()->fontMetrics().xHeight() * .2;
218  mTemporaryRubberBand->setWidth( scaleFactor );
219  mTemporaryRubberBand->setToGeometry( operationResults->representativeGeometry(), layer->crs() );
220  }
221  else
222  {
223  mTemporaryRubberBand.reset();
224  }
225  }
226  break;
227  }
228 
229  case Action::MoveNode:
230  {
231  if ( QgsAnnotationItem *item = annotationItemFromId( mSelectedItemLayerId, mSelectedItemId ) )
232  {
233  QgsAnnotationLayer *layer = annotationLayerFromId( mSelectedItemLayerId );
234  const QgsPointXY endPointLayer = toLayerCoordinates( layer, event->mapPoint() );
235  QgsAnnotationItemEditOperationMoveNode operation( mSelectedItemId, mTargetNode.id(), QgsPoint( mTargetNode.point() ), QgsPoint( endPointLayer ) );
236  std::unique_ptr< QgsAnnotationItemEditOperationTransientResults > operationResults( item->transientEditResults( &operation ) );
237  if ( operationResults )
238  {
239  mTemporaryRubberBand.reset( new QgsRubberBand( mCanvas, operationResults->representativeGeometry().type() ) );
240  const double scaleFactor = canvas()->fontMetrics().xHeight() * .2;
241  mTemporaryRubberBand->setWidth( scaleFactor );
242  mTemporaryRubberBand->setToGeometry( operationResults->representativeGeometry(), layer->crs() );
243  }
244  else
245  {
246  mTemporaryRubberBand.reset();
247  }
248  }
249  break;
250  }
251  }
252 
253 }
254 
256 {
257  switch ( mCurrentAction )
258  {
259  case Action::NoAction:
260  {
261  if ( event->button() != Qt::LeftButton )
262  return;
263 
264  if ( mHoveredItemId.isEmpty() || !mHoverRubberBand )
265  {
266  clearSelectedItem();
267  }
268  if ( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId )
269  {
270  // press is on selected item => move that item
271  if ( QgsAnnotationLayer *layer = annotationLayerFromId( mSelectedItemLayerId ) )
272  {
273  const QgsPointXY mapPoint = event->mapPoint();
274  QgsRectangle searchRect = QgsRectangle( mapPoint.x(), mapPoint.y(), mapPoint.x(), mapPoint.y() );
275  searchRect.grow( searchRadiusMU( canvas() ) );
276 
277  QgsAnnotationItemNode hoveredNode;
278  double currentNodeDistance = std::numeric_limits< double >::max();
279  mHoveredItemNodesSpatialIndex->intersects( searchRect, [&hoveredNode, &currentNodeDistance, &mapPoint, this]( int index )-> bool
280  {
281  const QgsAnnotationItemNode &thisNode = mHoveredItemNodes.at( index );
282  const double nodeDistance = thisNode.point().sqrDist( mapPoint );
283  if ( nodeDistance < currentNodeDistance )
284  {
285  hoveredNode = thisNode;
286  currentNodeDistance = nodeDistance;
287  }
288  return true;
289  } );
290 
291  mMoveStartPointCanvasCrs = mapPoint;
292  mMoveStartPointLayerCrs = toLayerCoordinates( layer, mMoveStartPointCanvasCrs );
293  if ( mHoverRubberBand )
294  mHoverRubberBand->hide();
295  if ( mSelectedRubberBand )
296  mSelectedRubberBand->hide();
297 
298  if ( hoveredNode.point().isEmpty() )
299  {
300  mCurrentAction = Action::MoveItem;
301  }
302  else
303  {
304  mCurrentAction = Action::MoveNode;
305  mTargetNode = hoveredNode;
306  }
307  }
308  }
309  else
310  {
311  // press is on a different item to selected item => select that item
312  mSelectedItemId = mHoveredItemId;
313  mSelectedItemLayerId = mHoveredItemLayerId;
314 
315  if ( !mSelectedRubberBand )
316  createSelectedItemBand();
317 
318  mSelectedRubberBand->copyPointsFrom( mHoverRubberBand );
319  mSelectedRubberBand->show();
320 
321  setCursor( Qt::OpenHandCursor );
322 
323  emit itemSelected( annotationLayerFromId( mSelectedItemLayerId ), mSelectedItemId );
324  }
325  break;
326  }
327 
328  case Action::MoveItem:
329  {
330  if ( event->button() == Qt::RightButton )
331  {
332  mCurrentAction = Action::NoAction;
333  mTemporaryRubberBand.reset();
334  if ( mSelectedRubberBand )
335  {
336  mSelectedRubberBand->setTranslationOffset( 0, 0 );
337  mSelectedRubberBand->show();
338  }
339  mHoveredItemNodeRubberBands.clear();
340  setCursor( Qt::ArrowCursor );
341  }
342  else if ( event->button() == Qt::LeftButton )
343  {
344  // apply move
345  if ( QgsAnnotationLayer *layer = annotationLayerFromId( mSelectedItemLayerId ) )
346  {
347  const QgsVector delta = toLayerCoordinates( layer, event->mapPoint() ) - mMoveStartPointLayerCrs;
348 
349  QgsAnnotationItemEditOperationTranslateItem operation( mSelectedItemId, delta.x(), delta.y() );
350  switch ( layer->applyEdit( &operation ) )
351  {
353  QgsProject::instance()->setDirty( true );
354  mRefreshSelectedItemAfterRedraw = true;
355  break;
358  break;
359  }
360  }
361 
362  mTemporaryRubberBand.reset();
363  mCurrentAction = Action::NoAction;
364  setCursor( Qt::ArrowCursor );
365  }
366  break;
367  }
368 
369  case Action::MoveNode:
370  {
371  if ( event->button() == Qt::RightButton )
372  {
373  mCurrentAction = Action::NoAction;
374  mTemporaryRubberBand.reset();
375  mHoveredItemNodeRubberBands.clear();
376  mTemporaryRubberBand.reset();
377  setCursor( Qt::ArrowCursor );
378  }
379  else if ( event->button() == Qt::LeftButton )
380  {
381  if ( QgsAnnotationLayer *layer = annotationLayerFromId( mSelectedItemLayerId ) )
382  {
383  const QgsPointXY endPointLayer = toLayerCoordinates( layer, event->mapPoint() );
384  QgsAnnotationItemEditOperationMoveNode operation( mSelectedItemId, mTargetNode.id(), QgsPoint( mTargetNode.point() ), QgsPoint( endPointLayer ) );
385  switch ( layer->applyEdit( &operation ) )
386  {
388  QgsProject::instance()->setDirty( true );
389  mRefreshSelectedItemAfterRedraw = true;
390  break;
391 
394  break;
395  }
396  }
397 
398  mTemporaryRubberBand.reset();
399  mHoveredItemNodeRubberBands.clear();
400  mHoveredItemNodes.clear();
401  mTemporaryRubberBand.reset();
402  mCurrentAction = Action::NoAction;
403  setCursor( Qt::ArrowCursor );
404  }
405  break;
406  }
407  }
408 }
409 
411 {
412  switch ( mCurrentAction )
413  {
414  case Action::NoAction:
415  case Action::MoveItem:
416  {
417  if ( event->button() != Qt::LeftButton )
418  return;
419 
420  mCurrentAction = Action::NoAction;
421  if ( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId )
422  {
423  // double-click on selected item => add node
424  if ( QgsAnnotationLayer *layer = annotationLayerFromId( mSelectedItemLayerId ) )
425  {
426  const QgsPointXY layerPoint = toLayerCoordinates( layer, event->mapPoint() );
427  QgsAnnotationItemEditOperationAddNode operation( mSelectedItemId, QgsPoint( layerPoint ) );
428  switch ( layer->applyEdit( &operation ) )
429  {
431  QgsProject::instance()->setDirty( true );
432  mRefreshSelectedItemAfterRedraw = true;
433  break;
434 
437  break;
438  }
439  }
440  }
441  else
442  {
443  // press is on a different item to selected item => select that item
444  mSelectedItemId = mHoveredItemId;
445  mSelectedItemLayerId = mHoveredItemLayerId;
446 
447  if ( !mSelectedRubberBand )
448  createSelectedItemBand();
449 
450  mSelectedRubberBand->copyPointsFrom( mHoverRubberBand );
451  mSelectedRubberBand->show();
452 
453  setCursor( Qt::OpenHandCursor );
454 
455  emit itemSelected( annotationLayerFromId( mSelectedItemLayerId ), mSelectedItemId );
456  }
457  break;
458  }
459 
460  case Action::MoveNode:
461  break;
462  }
463 }
464 
466 {
467  switch ( mCurrentAction )
468  {
469  case Action::NoAction:
470  {
471  if ( event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete )
472  {
473  QgsAnnotationLayer *layer = annotationLayerFromId( mSelectedItemLayerId );
474  if ( !layer || mSelectedItemId.isEmpty() )
475  return;
476 
477  layer->removeItem( mSelectedItemId );
478  clearSelectedItem();
479  clearHoveredItem();
480  event->ignore(); // disable default shortcut handling
481  }
482  else if ( event->key() == Qt::Key_Left
483  || event->key() == Qt::Key_Right
484  || event->key() == Qt::Key_Up
485  || event->key() == Qt::Key_Down )
486  {
487  QgsAnnotationLayer *layer = annotationLayerFromId( mSelectedItemLayerId );
488  if ( !layer )
489  return;
490 
491  const QSizeF deltaLayerCoordinates = deltaForKeyEvent( layer, mSelectedRubberBand->asGeometry().centroid().asPoint(), event );
492 
493  QgsAnnotationItemEditOperationTranslateItem operation( mSelectedItemId, deltaLayerCoordinates.width(), deltaLayerCoordinates.height() );
494  switch ( layer->applyEdit( &operation ) )
495  {
497  QgsProject::instance()->setDirty( true );
498  mRefreshSelectedItemAfterRedraw = true;
499  break;
502  break;
503  }
504  event->ignore(); // disable default shortcut handling (move map)
505  }
506  break;
507  }
508 
509  case Action::MoveNode:
510  {
511  if ( event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace )
512  {
513  if ( QgsAnnotationLayer *layer = annotationLayerFromId( mSelectedItemLayerId ) )
514  {
515  QgsAnnotationItemEditOperationDeleteNode operation( mSelectedItemId, mTargetNode.id(), QgsPoint( mTargetNode.point() ) );
516  switch ( layer->applyEdit( &operation ) )
517  {
519  QgsProject::instance()->setDirty( true );
520  mRefreshSelectedItemAfterRedraw = true;
521  break;
523  break;
525  QgsProject::instance()->setDirty( true );
526  break;
527  }
528  }
529 
530  mTemporaryRubberBand.reset();
531  mHoveredItemNodeRubberBands.clear();
532  mHoveredItemNodes.clear();
533  mTemporaryRubberBand.reset();
534  mCurrentAction = Action::NoAction;
535  setCursor( Qt::ArrowCursor );
536  event->ignore(); // disable default shortcut handling (delete vector feature)
537  break;
538  }
540  }
541 
542  case Action::MoveItem:
543  {
544  // warning -- fallthrough above!
545  if ( event->key() == Qt::Key_Escape )
546  {
547  mCurrentAction = Action::NoAction;
548  mTemporaryRubberBand.reset();
549  if ( mSelectedRubberBand )
550  {
551  mSelectedRubberBand->setTranslationOffset( 0, 0 );
552  mSelectedRubberBand->show();
553  }
554  mHoveredItemNodeRubberBands.clear();
555 
556  setCursor( Qt::ArrowCursor );
557  }
558  break;
559  }
560  }
561 }
562 
563 void QgsMapToolModifyAnnotation::onCanvasRefreshed()
564 {
565  if ( mRefreshSelectedItemAfterRedraw )
566  {
567  const QgsRenderedItemResults *renderedItemResults = canvas()->renderedItemResults( false );
568  if ( !renderedItemResults )
569  {
570  return;
571  }
572 
573  const QList<QgsRenderedItemDetails *> items = renderedItemResults->renderedItems();
574  auto it = std::find_if( items.begin(), items.end(), [this]( const QgsRenderedItemDetails * item )
575  {
576  if ( const QgsRenderedAnnotationItemDetails *annotationItem = dynamic_cast< const QgsRenderedAnnotationItemDetails *>( item ) )
577  {
578  if ( annotationItem->itemId() == mSelectedItemId && annotationItem->layerId() == mSelectedItemLayerId )
579  return true;
580  }
581  return false;
582  } );
583  if ( it != items.end() )
584  {
585  const QgsRectangle itemBounds = ( *it )->boundingBox();
586 
587  setHoveredItem( dynamic_cast< const QgsRenderedAnnotationItemDetails *>( *it ), itemBounds );
588  if ( !mSelectedRubberBand )
589  createSelectedItemBand();
590 
591  mSelectedRubberBand->copyPointsFrom( mHoverRubberBand );
592  mSelectedRubberBand->show();
593  }
594  }
595  mRefreshSelectedItemAfterRedraw = false;
596 }
597 
598 void QgsMapToolModifyAnnotation::setHoveredItem( const QgsRenderedAnnotationItemDetails *item, const QgsRectangle &itemMapBounds )
599 {
600  mHoveredItemNodeRubberBands.clear();
601  if ( mHoveredNodeRubberBand )
602  mHoveredNodeRubberBand->hide();
603  mHoveredItemId = item->itemId();
604  mHoveredItemLayerId = item->layerId();
605  if ( !mHoverRubberBand )
606  createHoverBand();
607 
608  mHoverRubberBand->show();
609 
610  mHoverRubberBand->reset( QgsWkbTypes::LineGeometry );
611  mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.xMinimum(), itemMapBounds.yMinimum() ) );
612  mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.xMaximum(), itemMapBounds.yMinimum() ) );
613  mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.xMaximum(), itemMapBounds.yMaximum() ) );
614  mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.xMinimum(), itemMapBounds.yMaximum() ) );
615  mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.xMinimum(), itemMapBounds.yMinimum() ) );
616 
617  QgsAnnotationLayer *layer = annotationLayerFromId( item->layerId() );
618  const QgsAnnotationItem *annotationItem = annotationItemFromId( item->layerId(), item->itemId() );
619  if ( !annotationItem )
620  return;
621 
622  QgsCoordinateTransform layerToMapTransform = QgsCoordinateTransform( layer->crs(), canvas()->mapSettings().destinationCrs(), canvas()->mapSettings().transformContext() );
623 
624  const double scaleFactor = canvas()->fontMetrics().xHeight() * .2;
625 
626  const QList< QgsAnnotationItemNode > itemNodes = annotationItem->nodes();
628 
629  vertexNodeBand->setIcon( QgsRubberBand::ICON_BOX );
630  vertexNodeBand->setWidth( scaleFactor );
631  vertexNodeBand->setIconSize( scaleFactor * 5 );
632  vertexNodeBand->setColor( QColor( 200, 0, 120, 255 ) );
633 
634  // store item nodes in a spatial index for quick searching
635  mHoveredItemNodesSpatialIndex = std::make_unique< QgsAnnotationItemNodesSpatialIndex >();
636  int index = 0;
637  mHoveredItemNodes.clear();
638  mHoveredItemNodes.reserve( itemNodes.size() );
639  for ( const QgsAnnotationItemNode &node : itemNodes )
640  {
641  QgsPointXY nodeMapPoint;
642  try
643  {
644  nodeMapPoint = layerToMapTransform.transform( node.point() );
645  }
646  catch ( QgsCsException & )
647  {
648  continue;
649  }
650 
651  switch ( node.type() )
652  {
654  vertexNodeBand->addPoint( nodeMapPoint );
655  break;
656  }
657 
658  mHoveredItemNodesSpatialIndex->insert( index, QgsRectangle( nodeMapPoint.x(), nodeMapPoint.y(),
659  nodeMapPoint.x(), nodeMapPoint.y() ) );
660 
661  QgsAnnotationItemNode transformedNode = node;
662  transformedNode.setPoint( nodeMapPoint );
663  mHoveredItemNodes.append( transformedNode );
664 
665  index++;
666  }
667 
668  mHoveredItemNodeRubberBands.emplace_back( vertexNodeBand );
669 }
670 
671 QSizeF QgsMapToolModifyAnnotation::deltaForKeyEvent( QgsAnnotationLayer *layer, const QgsPointXY &originalCanvasPoint, QKeyEvent *event )
672 {
673  const double canvasDpi = canvas()->window()->windowHandle()->screen()->physicalDotsPerInch();
674 
675  // increment used for cursor key item movement
676  double incrementPixels = 0.0;
677  if ( event->modifiers() & Qt::ShiftModifier )
678  {
679  //holding shift while pressing cursor keys results in a big step - 20 mm
680  incrementPixels = 20.0 / 25.4 * canvasDpi;
681  }
682  else if ( event->modifiers() & Qt::AltModifier )
683  {
684  //holding alt while pressing cursor keys results in a 1 pixel step
685  incrementPixels = 1;
686  }
687  else
688  {
689  // 5 mm
690  incrementPixels = 5.0 / 25.4 * canvasDpi;
691  }
692 
693  double deltaXPixels = 0;
694  double deltaYPixels = 0;
695  switch ( event->key() )
696  {
697  case Qt::Key_Left:
698  deltaXPixels = -incrementPixels;
699  break;
700  case Qt::Key_Right:
701  deltaXPixels = incrementPixels;
702  break;
703  case Qt::Key_Up:
704  deltaYPixels = -incrementPixels;
705  break;
706  case Qt::Key_Down:
707  deltaYPixels = incrementPixels;
708  break;
709  default:
710  break;
711  }
712 
713  const QgsPointXY beforeMoveMapPoint = canvas()->getCoordinateTransform()->toMapCoordinates( originalCanvasPoint.x(), originalCanvasPoint.y() );
714  const QgsPointXY beforeMoveLayerPoint = toLayerCoordinates( layer, beforeMoveMapPoint );
715 
716  const QgsPointXY afterMoveCanvasPoint( originalCanvasPoint.x() + deltaXPixels, originalCanvasPoint.y() + deltaYPixels );
717  const QgsPointXY afterMoveMapPoint = canvas()->getCoordinateTransform()->toMapCoordinates( afterMoveCanvasPoint.x(), afterMoveCanvasPoint.y() );
718  const QgsPointXY afterMoveLayerPoint = toLayerCoordinates( layer, afterMoveMapPoint );
719 
720  return QSizeF( afterMoveLayerPoint.x() - beforeMoveLayerPoint.x(), afterMoveLayerPoint.y() - beforeMoveLayerPoint.y() );
721 }
722 
723 const QgsRenderedAnnotationItemDetails *QgsMapToolModifyAnnotation::findClosestItemToPoint( const QgsPointXY &mapPoint, const QList<const QgsRenderedAnnotationItemDetails *> &items, QgsRectangle &bounds )
724 {
725  const QgsRenderedAnnotationItemDetails *closestItem = nullptr;
726  double closestItemDistance = std::numeric_limits< double >::max();
727  int closestItemZ = 0;
728 
729  for ( const QgsRenderedAnnotationItemDetails *item : items )
730  {
731  const QgsAnnotationItem *annotationItem = annotationItemFromId( item->layerId(), item->itemId() );
732  if ( !annotationItem )
733  continue;
734 
735  const QgsRectangle itemBounds = item->boundingBox();
736  const double itemDistance = itemBounds.contains( mapPoint ) ? 0 : itemBounds.distance( mapPoint );
737  if ( !closestItem || itemDistance < closestItemDistance || ( itemDistance == closestItemDistance && annotationItem->zIndex() > closestItemZ ) )
738  {
739  closestItem = item;
740  closestItemDistance = itemDistance;
741  closestItemZ = annotationItem->zIndex();
742  bounds = itemBounds;
743  }
744  }
745  return closestItem;
746 }
747 
748 QgsAnnotationLayer *QgsMapToolModifyAnnotation::annotationLayerFromId( const QString &layerId )
749 {
750  QgsAnnotationLayer *layer = qobject_cast< QgsAnnotationLayer * >( QgsProject::instance()->mapLayer( layerId ) );
751  if ( !layer && layerId == QgsProject::instance()->mainAnnotationLayer()->id() )
753  return layer;
754 }
755 
756 QgsAnnotationItem *QgsMapToolModifyAnnotation::annotationItemFromId( const QString &layerId, const QString &itemId )
757 {
758  QgsAnnotationLayer *layer = annotationLayerFromId( layerId );
759  return layer ? layer->item( itemId ) : nullptr;
760 }
761 
762 void QgsMapToolModifyAnnotation::clearHoveredItem()
763 {
764  if ( mHoverRubberBand )
765  mHoverRubberBand->hide();
766  if ( mHoveredNodeRubberBand )
767  mHoveredNodeRubberBand->hide();
768 
769  mHoveredItemId.clear();
770  mHoveredItemLayerId.clear();
771  mHoveredItemNodeRubberBands.clear();
772  mHoveredItemNodesSpatialIndex.reset();
773 
774  setCursor( Qt::ArrowCursor );
775 }
776 
777 void QgsMapToolModifyAnnotation::clearSelectedItem()
778 {
779  if ( mSelectedRubberBand )
780  mSelectedRubberBand->hide();
781 
782  const bool hadSelection = !mSelectedItemId.isEmpty();
783  mSelectedItemId.clear();
784  mSelectedItemLayerId.clear();
785  if ( hadSelection )
786  emit selectionCleared();
787 }
788 
789 void QgsMapToolModifyAnnotation::createHoverBand()
790 {
791  const double scaleFactor = canvas()->fontMetrics().xHeight() * .2;
792 
793  mHoverRubberBand.reset( new QgsRubberBand( mCanvas, QgsWkbTypes::LineGeometry ) );
794  mHoverRubberBand->setWidth( scaleFactor );
795  mHoverRubberBand->setSecondaryStrokeColor( QColor( 255, 255, 255, 100 ) );
796  mHoverRubberBand->setColor( QColor( 100, 100, 100, 155 ) );
797 }
798 
799 void QgsMapToolModifyAnnotation::createHoveredNodeBand()
800 {
801  const double scaleFactor = canvas()->fontMetrics().xHeight() * .2;
802 
803  mHoveredNodeRubberBand.reset( new QgsRubberBand( mCanvas, QgsWkbTypes::PointGeometry ) );
804  mHoveredNodeRubberBand->setIcon( QgsRubberBand::ICON_FULL_BOX );
805  mHoveredNodeRubberBand->setWidth( scaleFactor );
806  mHoveredNodeRubberBand->setIconSize( scaleFactor * 5 );
807  mHoveredNodeRubberBand->setColor( QColor( 200, 0, 120, 255 ) );
808 }
809 
810 void QgsMapToolModifyAnnotation::createSelectedItemBand()
811 {
812  const double scaleFactor = canvas()->fontMetrics().xHeight() * .2;
813 
814  mSelectedRubberBand.reset( new QgsRubberBand( mCanvas, QgsWkbTypes::LineGeometry ) );
815  mSelectedRubberBand->setWidth( scaleFactor );
816  mSelectedRubberBand->setSecondaryStrokeColor( QColor( 255, 255, 255, 100 ) );
817  mSelectedRubberBand->setColor( QColor( 50, 50, 50, 200 ) );
818 }
819 
QgsMapLayer::crs
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:79
QgsRectangle::distance
double distance(const QgsPointXY &point) const
Returns the distance from point to the nearest point on the boundary of the rectangle.
Definition: qgsrectangle.h:434
QgsAnnotationItem::nodes
virtual QList< QgsAnnotationItemNode > nodes() const
Returns the nodes for the item, used for editing the item.
Definition: qgsannotationitem.cpp:36
QgsPointXY::y
double y
Definition: qgspointxy.h:63
QgsMapTool::toLayerCoordinates
QgsPoint toLayerCoordinates(const QgsMapLayer *layer, const QgsPoint &point)
Transforms a point from map coordinates to layer coordinates.
Definition: qgsmaptool.cpp:62
QgsRenderedItemDetails::boundingBox
QgsRectangle boundingBox() const
Returns the bounding box of the item (in map units).
Definition: qgsrendereditemdetails.h:78
QObjectUniquePtr::reset
void reset(T *p=nullptr)
Will reset the managed pointer to p.
Definition: qobjectuniqueptr.h:179
QgsMapMouseEvent::mapPoint
QgsPointXY mapPoint() const
mapPoint returns the point in coordinates
Definition: qgsmapmouseevent.h:88
qgsrenderedannotationitemdetails.h
QgsMapToolModifyAnnotation::itemSelected
void itemSelected(QgsAnnotationLayer *layer, const QString &itemId)
Emitted when the selected item is changed.
qgsmapcanvas.h
QgsPoint
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:48
QgsRenderedItemResults::renderedItems
QList< QgsRenderedItemDetails * > renderedItems() const
Returns a list of all rendered items.
Definition: qgsrendereditemresults.cpp:109
QgsRubberBand
A class for drawing transient features (e.g. digitizing lines) on the map.
Definition: qgsrubberband.h:51
QgsRubberBand::setIconSize
void setIconSize(int iconSize)
Sets the size of the point icons.
Definition: qgsrubberband.cpp:95
QgsAnnotationItemEditOperationTranslateItem
Annotation item edit operation consisting of translating (moving) an item.
Definition: qgsannotationitemeditoperation.h:182
QgsAnnotationItemEditOperationDeleteNode
Annotation item edit operation consisting of deleting a node.
Definition: qgsannotationitemeditoperation.h:120
QgsAnnotationItemNode::setPoint
void setPoint(QgsPointXY point)
Sets the node's position, in geographic coordinates.
Definition: qgsannotationitemnode.h:93
QgsRectangle::yMinimum
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
QgsMapTool::setCursor
virtual void setCursor(const QCursor &cursor)
Sets a user defined cursor.
Definition: qgsmaptool.cpp:160
QgsMapCanvas
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:89
QgsGeometry::centroid
QgsGeometry centroid() const
Returns the center of mass of a geometry.
Definition: qgsgeometry.cpp:2284
QgsProject::instance
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:480
QgsMapToolAdvancedDigitizing::layer
virtual QgsMapLayer * layer() const
Returns the layer associated with the map tool.
Definition: qgsmaptooladvanceddigitizing.cpp:145
QgsRubberBand::ICON_BOX
@ ICON_BOX
A box is used to highlight points (□)
Definition: qgsrubberband.h:103
QgsPointXY::isEmpty
bool isEmpty() const SIP_HOLDGIL
Returns true if the geometry is empty.
Definition: qgspointxy.h:249
QgsMapToolModifyAnnotation::cadCanvasPressEvent
void cadCanvasPressEvent(QgsMapMouseEvent *event) override
Override this method when subclassing this class.
Definition: qgsmaptoolmodifyannotation.cpp:255
QgsMapCanvas::renderedItemResults
const QgsRenderedItemResults * renderedItemResults(bool allowOutdatedResults=true) const
Gets access to the rendered item results (may be nullptr), which includes the results of rendering an...
Definition: qgsmapcanvas.cpp:546
FALLTHROUGH
#define FALLTHROUGH
Definition: qgis.h:2847
QgsMapTool::canvas
QgsMapCanvas * canvas() const
returns pointer to the tool's map canvas
Definition: qgsmaptool.cpp:215
QgsMapToPixel::toMapCoordinates
QgsPointXY toMapCoordinates(int x, int y) const
Transforms device coordinates to map (world) coordinates.
Definition: qgsmaptopixel.h:173
QgsMapTool::mCanvas
QPointer< QgsMapCanvas > mCanvas
The pointer to the map canvas.
Definition: qgsmaptool.h:336
QgsRectangle
A rectangle specified with double values.
Definition: qgsrectangle.h:41
qgssnapindicator.h
QgsSnapIndicator
Class that shows snapping marker on map canvas for the current snapping match.
Definition: qgssnapindicator.h:32
QgsRubberBand::reset
void reset(QgsWkbTypes::GeometryType geometryType=QgsWkbTypes::LineGeometry)
Clears all the geometries in this rubberband.
Definition: qgsrubberband.cpp:110
qgsrubberband.h
Qgis::AnnotationItemEditOperationResult::ItemCleared
@ ItemCleared
The operation results in the item being cleared, and the item should be removed from the layer as a r...
QgsRectangle::xMaximum
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:183
qgsmaptoolmodifyannotation.h
QgsMapMouseEvent::mapPointMatch
QgsPointLocator::Match mapPointMatch() const
Returns the matching data from the most recently snapped point.
Definition: qgsmapmouseevent.h:97
QgsMapToolModifyAnnotation::cadCanvasMoveEvent
void cadCanvasMoveEvent(QgsMapMouseEvent *event) override
Override this method when subclassing this class.
Definition: qgsmaptoolmodifyannotation.cpp:124
QgsCsException
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:65
QgsMapToolModifyAnnotation::canvasDoubleClickEvent
void canvasDoubleClickEvent(QgsMapMouseEvent *event) override
Mouse double-click event for overriding. Default implementation does nothing.
Definition: qgsmaptoolmodifyannotation.cpp:410
QgsAnnotationItemNode::point
QgsPointXY point() const
Returns the node's position, in geographic coordinates.
Definition: qgsannotationitemnode.h:84
QgsRubberBand::setTranslationOffset
void setTranslationOffset(double dx, double dy)
Adds translation to original coordinates (all in map coordinates)
Definition: qgsrubberband.cpp:701
QgsRenderedItemDetails
Base class for detailed information about a rendered item.
Definition: qgsrendereditemdetails.h:36
QgsRenderedItemDetails::layerId
QString layerId() const
Returns the layer ID of the associated map layer.
Definition: qgsrendereditemdetails.h:71
QgsMapToolModifyAnnotation::~QgsMapToolModifyAnnotation
~QgsMapToolModifyAnnotation() override
QgsVector::x
double x() const SIP_HOLDGIL
Returns the vector's x-component.
Definition: qgsvector.h:161
QgsRectangle::contains
bool contains(const QgsRectangle &rect) const SIP_HOLDGIL
Returns true when rectangle contains other rectangle.
Definition: qgsrectangle.h:363
QgsVector::y
double y() const SIP_HOLDGIL
Returns the vector's y-component.
Definition: qgsvector.h:170
QgsAnnotationItemNode::id
QgsVertexId id() const
Returns the ID number of the node, used for uniquely identifying the node in the item.
Definition: qgsannotationitemnode.h:75
QgsRubberBand::setColor
void setColor(const QColor &color)
Sets the color for the rubberband.
Definition: qgsrubberband.cpp:54
QgsRenderedAnnotationItemDetails
Contains information about a rendered annotation item.
Definition: qgsrenderedannotationitemdetails.h:29
QgsProject::setDirty
void setDirty(bool b=true)
Flag the project as dirty (modified).
Definition: qgsproject.cpp:576
qgsrendereditemdetails.h
QgsMapToolModifyAnnotation::QgsMapToolModifyAnnotation
QgsMapToolModifyAnnotation(QgsMapCanvas *canvas, QgsAdvancedDigitizingDockWidget *cadDockWidget)
Constructor for QgsMapToolModifyAnnotation.
Definition: qgsmaptoolmodifyannotation.cpp:106
QgsRubberBand::addPoint
void addPoint(const QgsPointXY &p, bool doUpdate=true, int geometryIndex=0, int ringIndex=0)
Adds a vertex to the rubberband and update canvas.
Definition: qgsrubberband.cpp:118
QgsRectangle::xMinimum
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
QgsMapToolModifyAnnotation::deactivate
void deactivate() override
Unregisters this maptool from the cad dock widget.
Definition: qgsmaptoolmodifyannotation.cpp:116
qgsannotationlayer.h
QgsMapCanvas::getCoordinateTransform
const QgsMapToPixel * getCoordinateTransform()
Gets the current coordinate transform.
Definition: qgsmapcanvas.cpp:379
QgsAdvancedDigitizingDockWidget
The QgsAdvancedDigitizingDockWidget class is a dockable widget used to handle the CAD tools on top of...
Definition: qgsadvanceddigitizingdockwidget.h:50
QgsAnnotationItemEditOperationMoveNode
Annotation item edit operation consisting of moving a node.
Definition: qgsannotationitemeditoperation.h:75
QgsRubberBand::ICON_FULL_BOX
@ ICON_FULL_BOX
A full box is used to highlight points (■)
Definition: qgsrubberband.h:113
qgsannotationitemnode.h
QgsPointLocator::Match
Definition: qgspointlocator.h:187
QgsRenderedAnnotationItemDetails::itemId
QString itemId() const
Returns the item ID of the associated annotation item.
Definition: qgsrenderedannotationitemdetails.h:61
QgsPointXY
A class to represent a 2D point.
Definition: qgspointxy.h:58
QgsGeometry::asPoint
QgsPointXY asPoint() const
Returns the contents of the geometry as a 2-dimensional point.
Definition: qgsgeometry.cpp:1662
Qgis::AnnotationItemNodeType::VertexHandle
@ VertexHandle
Node is a handle for manipulating vertices.
QgsAnnotationItem::zIndex
int zIndex() const
Returns the item's z index, which controls the order in which annotation items are rendered in the la...
Definition: qgsannotationitem.h:165
QgsMapMouseEvent
A QgsMapMouseEvent is the result of a user interaction with the mouse on a QgsMapCanvas....
Definition: qgsmapmouseevent.h:35
QgsWkbTypes::LineGeometry
@ LineGeometry
Definition: qgswkbtypes.h:143
Qgis::AnnotationItemEditOperationResult::Success
@ Success
Item was modified successfully.
QgsWkbTypes::PointGeometry
@ PointGeometry
Definition: qgswkbtypes.h:142
QgsRubberBand::setWidth
void setWidth(int width)
Sets the width of the line.
Definition: qgsrubberband.cpp:78
QgsRectangle::yMaximum
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
QgsAnnotationItemNode
Contains information about a node used for editing an annotation item.
Definition: qgsannotationitemnode.h:31
QgsVector
A class to represent a vector. Currently no Z axis / 2.5D support is implemented.
Definition: qgsvector.h:29
QgsAnnotationLayer
Represents a map layer containing a set of georeferenced annotations, e.g. markers,...
Definition: qgsannotationlayer.h:46
QgsPointXY::x
double x
Definition: qgspointxy.h:62
QgsRenderedItemResults::renderedAnnotationItemsInBounds
QList< const QgsRenderedAnnotationItemDetails * > renderedAnnotationItemsInBounds(const QgsRectangle &bounds) const
Returns a list with details of the rendered annotation items within the specified bounds.
Definition: qgsrendereditemresults.cpp:122
QgsRubberBand::setIcon
void setIcon(IconType icon)
Sets the icon type to highlight point geometries.
Definition: qgsrubberband.cpp:83
QgsMapTool::searchRadiusMU
static double searchRadiusMU(const QgsRenderContext &context)
Gets search radius in map units for given context.
Definition: qgsmaptool.cpp:232
QgsMapToolAdvancedDigitizing::deactivate
void deactivate() override
Unregisters this maptool from the cad dock widget.
Definition: qgsmaptooladvanceddigitizing.cpp:136
qgsannotationitem.h
QgsRubberBand::setToGeometry
void setToGeometry(const QgsGeometry &geom, QgsVectorLayer *layer)
Sets this rubber band to geom.
Definition: qgsrubberband.cpp:264
QgsRectangle::grow
void grow(double delta)
Grows the rectangle in place by the specified amount.
Definition: qgsrectangle.h:296
QgsRenderedItemResults
Stores collated details of rendered items during a map rendering operation.
Definition: qgsrendereditemresults.h:42
QgsCoordinateTransform::transform
QgsPointXY transform(const QgsPointXY &point, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const SIP_THROW(QgsCsException)
Transform the point from the source CRS to the destination CRS.
Definition: qgscoordinatetransform.cpp:272
QgsMapToolModifyAnnotation::selectionCleared
void selectionCleared()
Emitted when the selected item is cleared;.
QgsAnnotationItemEditOperationAddNode
Annotation item edit operation consisting of adding a node.
Definition: qgsannotationitemeditoperation.h:154
qgsannotationitemeditoperation.h
QgsCoordinateTransform
Class for doing transforms between two map coordinate systems.
Definition: qgscoordinatetransform.h:57
qgsmapmouseevent.h
QgsRubberBand::setSecondaryStrokeColor
void setSecondaryStrokeColor(const QColor &color)
Sets a secondary stroke color for the rubberband which will be drawn under the main stroke color.
Definition: qgsrubberband.cpp:73
QgsMapToolAdvancedDigitizing
The QgsMapToolAdvancedDigitizing class is a QgsMapTool which gives event directly in map coordinates ...
Definition: qgsmaptooladvanceddigitizing.h:36
QgsRubberBand::copyPointsFrom
void copyPointsFrom(const QgsRubberBand *other)
Copies the points from another rubber band.
Definition: qgsrubberband.cpp:439
QgsProject::mainAnnotationLayer
QgsAnnotationLayer * mainAnnotationLayer()
Returns the main annotation layer associated with the project.
Definition: qgsproject.cpp:3925
QgsRubberBand::asGeometry
QgsGeometry asGeometry() const
Returns the rubberband as a Geometry.
Definition: qgsrubberband.cpp:746
qgsproject.h
QgsAnnotationItem
Abstract base class for annotation items which are drawn with QgsAnnotationLayers.
Definition: qgsannotationitem.h:42
qgsrendereditemresults.h
Qgis::AnnotationItemEditOperationResult::Invalid
@ Invalid
Operation has invalid parameters for the item, no change occurred.
QgsMapCanvas::mapCanvasRefreshed
void mapCanvasRefreshed()
Emitted when canvas finished a refresh request.
QgsMapToolModifyAnnotation::keyPressEvent
void keyPressEvent(QKeyEvent *event) override
Key event for overriding. Default implementation does nothing.
Definition: qgsmaptoolmodifyannotation.cpp:465