QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
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 
void reset(T *p=nullptr)
Will reset the managed pointer to p.
@ VertexHandle
Node is a handle for manipulating vertices.
@ 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...
The QgsAdvancedDigitizingDockWidget class is a dockable widget used to handle the CAD tools on top of...
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.
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 > nodes() const
Returns the nodes for the item, used for editing the item.
int zIndex() const
Returns the item's z index, which controls the order in which annotation items are rendered in the la...
Represents a map layer containing a set of georeferenced annotations, e.g.
Class for doing transforms between two map coordinate systems.
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.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:66
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.
Definition: qgsmapcanvas.h:89
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
Definition: qgsmaplayer.h:79
A QgsMapMouseEvent is the result of a user interaction with the mouse on a QgsMapCanvas.
QgsPointXY mapPoint() const
mapPoint returns the point in 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.
The QgsMapToolAdvancedDigitizing class is a QgsMapTool which gives event directly in map coordinates ...
void deactivate() override
Unregisters this maptool from the cad dock widget.
virtual QgsMapLayer * layer() const
Returns the layer associated with the map tool.
void itemSelected(QgsAnnotationLayer *layer, const QString &itemId)
Emitted when the selected item is changed.
void cadCanvasPressEvent(QgsMapMouseEvent *event) override
Override this method when subclassing this class.
void keyPressEvent(QKeyEvent *event) override
Key event for overriding. Default implementation does nothing.
~QgsMapToolModifyAnnotation() override
void deactivate() override
Unregisters this maptool from the cad dock widget.
void canvasDoubleClickEvent(QgsMapMouseEvent *event) override
Mouse double-click event for overriding. Default implementation does nothing.
void selectionCleared()
Emitted when the selected item is cleared;.
QgsMapToolModifyAnnotation(QgsMapCanvas *canvas, QgsAdvancedDigitizingDockWidget *cadDockWidget)
Constructor for QgsMapToolModifyAnnotation.
void cadCanvasMoveEvent(QgsMapMouseEvent *event) override
Override this method when subclassing this class.
QgsPoint toLayerCoordinates(const QgsMapLayer *layer, const QgsPoint &point)
Transforms a point from map coordinates to layer coordinates.
Definition: qgsmaptool.cpp:62
QgsMapCanvas * mCanvas
The pointer to the map canvas.
Definition: qgsmaptool.h:336
QgsMapCanvas * canvas() const
returns pointer to the tool's map canvas
Definition: qgsmaptool.cpp:215
virtual void setCursor(const QCursor &cursor)
Sets a user defined cursor.
Definition: qgsmaptool.cpp:160
static double searchRadiusMU(const QgsRenderContext &context)
Gets search radius in map units for given context.
Definition: qgsmaptool.cpp:232
A class to represent a 2D point.
Definition: qgspointxy.h:59
bool isEmpty() const SIP_HOLDGIL
Returns true if the geometry is empty.
Definition: qgspointxy.h:249
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:49
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:467
QgsAnnotationLayer * mainAnnotationLayer()
Returns the main annotation layer associated with the project.
void setDirty(bool b=true)
Flag the project as dirty (modified).
Definition: qgsproject.cpp:518
A rectangle specified with double values.
Definition: qgsrectangle.h:42
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:183
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
void grow(double delta)
Grows the rectangle in place by the specified amount.
Definition: qgsrectangle.h:296
bool contains(const QgsRectangle &rect) const SIP_HOLDGIL
Returns true when rectangle contains other rectangle.
Definition: qgsrectangle.h:363
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
Contains information about a rendered annotation item.
QString itemId() const
Returns the item ID of the associated annotation item.
Base class for detailed information about a rendered item.
QString layerId() const
Returns the layer ID of the associated map layer.
QgsRectangle boundingBox() const
Returns the bounding box of the item (in map units).
Stores collated details of rendered items during a map rendering operation.
QList< const QgsRenderedAnnotationItemDetails * > renderedAnnotationItemsInBounds(const QgsRectangle &bounds) const
Returns a list with details of the rendered annotation items within the specified bounds.
QList< QgsRenderedItemDetails * > renderedItems() const
Returns a list of all rendered items.
A class for drawing transient features (e.g.
Definition: qgsrubberband.h:52
void setWidth(int width)
Sets the width of the line.
QgsGeometry asGeometry() const
Returns the rubberband as a Geometry.
void setSecondaryStrokeColor(const QColor &color)
Sets a secondary stroke color for the rubberband which will be drawn under the main stroke color.
@ ICON_FULL_BOX
A full box is used to highlight points (■)
@ ICON_BOX
A box is used to highlight points (□)
Definition: qgsrubberband.h:97
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 setIconSize(int iconSize)
Sets the size of the point icons.
void setIcon(IconType icon)
Sets the icon type to highlight point geometries.
void reset(QgsWkbTypes::GeometryType geometryType=QgsWkbTypes::LineGeometry)
Clears all the geometries in this rubberband.
void copyPointsFrom(const QgsRubberBand *other)
Copies the points from another rubber band.
void setTranslationOffset(double dx, double dy)
Adds translation to original coordinates (all in map coordinates)
void addPoint(const QgsPointXY &p, bool doUpdate=true, int geometryIndex=0, int ringIndex=0)
Adds a vertex to the rubberband and update canvas.
Class that shows snapping marker on map canvas for the current snapping match.
A class to represent a vector.
Definition: qgsvector.h:30
double y() const SIP_HOLDGIL
Returns the vector's y-component.
Definition: qgsvector.h:156
double x() const SIP_HOLDGIL
Returns the vector's x-component.
Definition: qgsvector.h:147
#define FALLTHROUGH
Definition: qgis.h:1769