QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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"
23#include "qgsannotationlayer.h"
24#include "qgsproject.h"
26#include "qgsannotationitem.h"
29#include "qgssnapindicator.h"
30#include "RTree.h"
31#include <QTransform>
32#include <QWindow>
33#include <QScreen>
34
36class 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 {
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 {
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 {
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 {
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 {
520 mRefreshSelectedItemAfterRedraw = true;
521 break;
523 break;
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
563void 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
598void 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
671QSizeF 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
723const 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
748QgsAnnotationLayer *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
756QgsAnnotationItem *QgsMapToolModifyAnnotation::annotationItemFromId( const QString &layerId, const QString &itemId )
757{
758 QgsAnnotationLayer *layer = annotationLayerFromId( layerId );
759 return layer ? layer->item( itemId ) : nullptr;
760}
761
762void 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
777void 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
789void 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
799void 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
810void 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:90
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
QPointer< QgsMapCanvas > mCanvas
The pointer to the map canvas.
Definition: qgsmaptool.h:338
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:477
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:573
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 (□)
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:3088