QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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();
121 clearSelectedItem();
123}
124
126{
127 event->snapPoint();
128 mSnapIndicator->setMatch( event->mapPointMatch() );
129
130 const QgsPointXY mapPoint = event->mapPoint();
131
132 switch ( mCurrentAction )
133 {
134 case Action::NoAction:
135 {
136 QgsRectangle searchRect = QgsRectangle( mapPoint.x(), mapPoint.y(), mapPoint.x(), mapPoint.y() );
137 searchRect.grow( searchRadiusMU( canvas() ) );
138
139 const QgsRenderedItemResults *renderedItemResults = canvas()->renderedItemResults( false );
140 if ( !renderedItemResults )
141 {
142 clearHoveredItem();
143 return;
144 }
145
146 const QList<const QgsRenderedAnnotationItemDetails *> items = renderedItemResults->renderedAnnotationItemsInBounds( searchRect );
147 if ( items.empty() )
148 {
149 clearHoveredItem();
150 return;
151 }
152
153 // find closest item
154 QgsRectangle itemBounds;
155 const QgsRenderedAnnotationItemDetails *closestItem = findClosestItemToPoint( mapPoint, items, itemBounds );
156 if ( !closestItem )
157 {
158 clearHoveredItem();
159 return;
160 }
161
162 if ( closestItem->itemId() != mHoveredItemId || closestItem->layerId() != mHoveredItemLayerId )
163 {
164 setHoveredItem( closestItem, itemBounds );
165 }
166
167 // track hovered node too!... here we want to identify the closest node to the cursor position
168 QgsAnnotationItemNode hoveredNode;
169 if ( closestItem->itemId() == mSelectedItemId && closestItem->layerId() == mSelectedItemLayerId )
170 {
171 double currentNodeDistance = std::numeric_limits< double >::max();
172 mHoveredItemNodesSpatialIndex->intersects( searchRect, [&hoveredNode, &currentNodeDistance, &mapPoint, this]( int index )-> bool
173 {
174 const QgsAnnotationItemNode &thisNode = mHoveredItemNodes.at( index );
175 const double nodeDistance = thisNode.point().sqrDist( mapPoint );
176 if ( nodeDistance < currentNodeDistance )
177 {
178 hoveredNode = thisNode;
179 currentNodeDistance = nodeDistance;
180 }
181 return true;
182 } );
183 }
184
185 if ( hoveredNode.point().isEmpty() )
186 {
187 // no hovered node
188 if ( mHoveredNodeRubberBand )
189 mHoveredNodeRubberBand->hide();
190 setCursor( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId ? Qt::OpenHandCursor : Qt::ArrowCursor );
191 }
192 else
193 {
194 if ( !mHoveredNodeRubberBand )
195 createHoveredNodeBand();
196
197 mHoveredNodeRubberBand->reset( Qgis::GeometryType::Point );
198 mHoveredNodeRubberBand->addPoint( hoveredNode.point() );
199 mHoveredNodeRubberBand->show();
200
201 setCursor( Qt::ArrowCursor );
202 }
203 break;
204 }
205
206 case Action::MoveItem:
207 {
208 if ( QgsAnnotationItem *item = annotationItemFromId( mSelectedItemLayerId, mSelectedItemId ) )
209 {
210 QgsAnnotationLayer *layer = annotationLayerFromId( mSelectedItemLayerId );
211 const QgsVector delta = toLayerCoordinates( layer, event->mapPoint() ) - mMoveStartPointLayerCrs;
212
213 QgsAnnotationItemEditOperationTranslateItem operation( mSelectedItemId, delta.x(), delta.y() );
214 std::unique_ptr< QgsAnnotationItemEditOperationTransientResults > operationResults( item->transientEditResults( &operation ) );
215 if ( operationResults )
216 {
217 mTemporaryRubberBand.reset( new QgsRubberBand( mCanvas, operationResults->representativeGeometry().type() ) );
218 const double scaleFactor = canvas()->fontMetrics().xHeight() * .2;
219 mTemporaryRubberBand->setWidth( scaleFactor );
220 mTemporaryRubberBand->setToGeometry( operationResults->representativeGeometry(), layer->crs() );
221 }
222 else
223 {
224 mTemporaryRubberBand.reset();
225 }
226 }
227 break;
228 }
229
230 case Action::MoveNode:
231 {
232 if ( QgsAnnotationItem *item = annotationItemFromId( mSelectedItemLayerId, mSelectedItemId ) )
233 {
234 QgsAnnotationLayer *layer = annotationLayerFromId( mSelectedItemLayerId );
235 const QgsPointXY endPointLayer = toLayerCoordinates( layer, event->mapPoint() );
236 QgsAnnotationItemEditOperationMoveNode operation( mSelectedItemId, mTargetNode.id(), QgsPoint( mTargetNode.point() ), QgsPoint( endPointLayer ) );
237 std::unique_ptr< QgsAnnotationItemEditOperationTransientResults > operationResults( item->transientEditResults( &operation ) );
238 if ( operationResults )
239 {
240 mTemporaryRubberBand.reset( new QgsRubberBand( mCanvas, operationResults->representativeGeometry().type() ) );
241 const double scaleFactor = canvas()->fontMetrics().xHeight() * .2;
242 mTemporaryRubberBand->setWidth( scaleFactor );
243 mTemporaryRubberBand->setToGeometry( operationResults->representativeGeometry(), layer->crs() );
244 }
245 else
246 {
247 mTemporaryRubberBand.reset();
248 }
249 }
250 break;
251 }
252 }
253
254}
255
257{
258 switch ( mCurrentAction )
259 {
260 case Action::NoAction:
261 {
262 if ( event->button() != Qt::LeftButton )
263 return;
264
265 if ( mHoveredItemId.isEmpty() || !mHoverRubberBand )
266 {
267 clearSelectedItem();
268 }
269 if ( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId )
270 {
271 // press is on selected item => move that item
272 if ( QgsAnnotationLayer *layer = annotationLayerFromId( mSelectedItemLayerId ) )
273 {
274 const QgsPointXY mapPoint = event->mapPoint();
275 QgsRectangle searchRect = QgsRectangle( mapPoint.x(), mapPoint.y(), mapPoint.x(), mapPoint.y() );
276 searchRect.grow( searchRadiusMU( canvas() ) );
277
278 QgsAnnotationItemNode hoveredNode;
279 double currentNodeDistance = std::numeric_limits< double >::max();
280 mHoveredItemNodesSpatialIndex->intersects( searchRect, [&hoveredNode, &currentNodeDistance, &mapPoint, this]( int index )-> bool
281 {
282 const QgsAnnotationItemNode &thisNode = mHoveredItemNodes.at( index );
283 const double nodeDistance = thisNode.point().sqrDist( mapPoint );
284 if ( nodeDistance < currentNodeDistance )
285 {
286 hoveredNode = thisNode;
287 currentNodeDistance = nodeDistance;
288 }
289 return true;
290 } );
291
292 mMoveStartPointCanvasCrs = mapPoint;
293 mMoveStartPointLayerCrs = toLayerCoordinates( layer, mMoveStartPointCanvasCrs );
294 if ( mHoverRubberBand )
295 mHoverRubberBand->hide();
296 if ( mSelectedRubberBand )
297 mSelectedRubberBand->hide();
298
299 if ( hoveredNode.point().isEmpty() )
300 {
301 mCurrentAction = Action::MoveItem;
302 }
303 else
304 {
305 mCurrentAction = Action::MoveNode;
306 mTargetNode = hoveredNode;
307 }
308 }
309 }
310 else
311 {
312 // press is on a different item to selected item => select that item
313 mSelectedItemId = mHoveredItemId;
314 mSelectedItemLayerId = mHoveredItemLayerId;
315
316 if ( !mSelectedRubberBand )
317 createSelectedItemBand();
318
319 mSelectedRubberBand->copyPointsFrom( mHoverRubberBand );
320 mSelectedRubberBand->show();
321
322 setCursor( Qt::OpenHandCursor );
323
324 emit itemSelected( annotationLayerFromId( mSelectedItemLayerId ), mSelectedItemId );
325 }
326 break;
327 }
328
329 case Action::MoveItem:
330 {
331 if ( event->button() == Qt::RightButton )
332 {
333 mCurrentAction = Action::NoAction;
334 mTemporaryRubberBand.reset();
335 if ( mSelectedRubberBand )
336 {
337 mSelectedRubberBand->setTranslationOffset( 0, 0 );
338 mSelectedRubberBand->show();
339 }
340 mHoveredItemNodeRubberBands.clear();
341 setCursor( Qt::ArrowCursor );
342 }
343 else if ( event->button() == Qt::LeftButton )
344 {
345 // apply move
346 if ( QgsAnnotationLayer *layer = annotationLayerFromId( mSelectedItemLayerId ) )
347 {
348 const QgsVector delta = toLayerCoordinates( layer, event->mapPoint() ) - mMoveStartPointLayerCrs;
349
350 QgsAnnotationItemEditOperationTranslateItem operation( mSelectedItemId, delta.x(), delta.y() );
351 switch ( layer->applyEdit( &operation ) )
352 {
355 mRefreshSelectedItemAfterRedraw = true;
356 break;
359 break;
360 }
361 }
362
363 mTemporaryRubberBand.reset();
364 mCurrentAction = Action::NoAction;
365 setCursor( Qt::ArrowCursor );
366 }
367 break;
368 }
369
370 case Action::MoveNode:
371 {
372 if ( event->button() == Qt::RightButton )
373 {
374 mCurrentAction = Action::NoAction;
375 mTemporaryRubberBand.reset();
376 mHoveredItemNodeRubberBands.clear();
377 mTemporaryRubberBand.reset();
378 setCursor( Qt::ArrowCursor );
379 }
380 else if ( event->button() == Qt::LeftButton )
381 {
382 if ( QgsAnnotationLayer *layer = annotationLayerFromId( mSelectedItemLayerId ) )
383 {
384 const QgsPointXY endPointLayer = toLayerCoordinates( layer, event->mapPoint() );
385 QgsAnnotationItemEditOperationMoveNode operation( mSelectedItemId, mTargetNode.id(), QgsPoint( mTargetNode.point() ), QgsPoint( endPointLayer ) );
386 switch ( layer->applyEdit( &operation ) )
387 {
390 mRefreshSelectedItemAfterRedraw = true;
391 break;
392
395 break;
396 }
397 }
398
399 mTemporaryRubberBand.reset();
400 mHoveredItemNodeRubberBands.clear();
401 mHoveredItemNodes.clear();
402 mTemporaryRubberBand.reset();
403 mCurrentAction = Action::NoAction;
404 setCursor( Qt::ArrowCursor );
405 }
406 break;
407 }
408 }
409}
410
412{
413 switch ( mCurrentAction )
414 {
415 case Action::NoAction:
416 case Action::MoveItem:
417 {
418 if ( event->button() != Qt::LeftButton )
419 return;
420
421 mCurrentAction = Action::NoAction;
422 if ( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId )
423 {
424 // double-click on selected item => add node
425 if ( QgsAnnotationLayer *layer = annotationLayerFromId( mSelectedItemLayerId ) )
426 {
427 const QgsPointXY layerPoint = toLayerCoordinates( layer, event->mapPoint() );
428 QgsAnnotationItemEditOperationAddNode operation( mSelectedItemId, QgsPoint( layerPoint ) );
429 switch ( layer->applyEdit( &operation ) )
430 {
433 mRefreshSelectedItemAfterRedraw = true;
434 break;
435
438 break;
439 }
440 }
441 }
442 else
443 {
444 // press is on a different item to selected item => select that item
445 mSelectedItemId = mHoveredItemId;
446 mSelectedItemLayerId = mHoveredItemLayerId;
447
448 if ( !mSelectedRubberBand )
449 createSelectedItemBand();
450
451 mSelectedRubberBand->copyPointsFrom( mHoverRubberBand );
452 mSelectedRubberBand->show();
453
454 setCursor( Qt::OpenHandCursor );
455
456 emit itemSelected( annotationLayerFromId( mSelectedItemLayerId ), mSelectedItemId );
457 }
458 break;
459 }
460
461 case Action::MoveNode:
462 break;
463 }
464}
465
467{
468 switch ( mCurrentAction )
469 {
470 case Action::NoAction:
471 {
472 if ( event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete )
473 {
474 QgsAnnotationLayer *layer = annotationLayerFromId( mSelectedItemLayerId );
475 if ( !layer || mSelectedItemId.isEmpty() )
476 return;
477
478 layer->removeItem( mSelectedItemId );
479 clearSelectedItem();
480 clearHoveredItem();
481 event->ignore(); // disable default shortcut handling
482 }
483 else if ( event->key() == Qt::Key_Left
484 || event->key() == Qt::Key_Right
485 || event->key() == Qt::Key_Up
486 || event->key() == Qt::Key_Down )
487 {
488 QgsAnnotationLayer *layer = annotationLayerFromId( mSelectedItemLayerId );
489 if ( !layer )
490 return;
491
492 const QSizeF deltaLayerCoordinates = deltaForKeyEvent( layer, mSelectedRubberBand->asGeometry().centroid().asPoint(), event );
493
494 QgsAnnotationItemEditOperationTranslateItem operation( mSelectedItemId, deltaLayerCoordinates.width(), deltaLayerCoordinates.height() );
495 switch ( layer->applyEdit( &operation ) )
496 {
499 mRefreshSelectedItemAfterRedraw = true;
500 break;
503 break;
504 }
505 event->ignore(); // disable default shortcut handling (move map)
506 }
507 break;
508 }
509
510 case Action::MoveNode:
511 {
512 if ( event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace )
513 {
514 if ( QgsAnnotationLayer *layer = annotationLayerFromId( mSelectedItemLayerId ) )
515 {
516 QgsAnnotationItemEditOperationDeleteNode operation( mSelectedItemId, mTargetNode.id(), QgsPoint( mTargetNode.point() ) );
517 switch ( layer->applyEdit( &operation ) )
518 {
521 mRefreshSelectedItemAfterRedraw = true;
522 break;
524 break;
527 break;
528 }
529 }
530
531 mTemporaryRubberBand.reset();
532 mHoveredItemNodeRubberBands.clear();
533 mHoveredItemNodes.clear();
534 mTemporaryRubberBand.reset();
535 mCurrentAction = Action::NoAction;
536 setCursor( Qt::ArrowCursor );
537 event->ignore(); // disable default shortcut handling (delete vector feature)
538 break;
539 }
540 [[fallthrough]];
541 }
542
543 case Action::MoveItem:
544 {
545 // warning -- fallthrough above!
546 if ( event->key() == Qt::Key_Escape )
547 {
548 mCurrentAction = Action::NoAction;
549 mTemporaryRubberBand.reset();
550 if ( mSelectedRubberBand )
551 {
552 mSelectedRubberBand->setTranslationOffset( 0, 0 );
553 mSelectedRubberBand->show();
554 }
555 mHoveredItemNodeRubberBands.clear();
556
557 setCursor( Qt::ArrowCursor );
558 }
559 break;
560 }
561 }
562}
563
564void QgsMapToolModifyAnnotation::onCanvasRefreshed()
565{
566 if ( mRefreshSelectedItemAfterRedraw )
567 {
568 const QgsRenderedItemResults *renderedItemResults = canvas()->renderedItemResults( false );
569 if ( !renderedItemResults )
570 {
571 return;
572 }
573
574 const QList<QgsRenderedItemDetails *> items = renderedItemResults->renderedItems();
575 auto it = std::find_if( items.begin(), items.end(), [this]( const QgsRenderedItemDetails * item )
576 {
577 if ( const QgsRenderedAnnotationItemDetails *annotationItem = dynamic_cast< const QgsRenderedAnnotationItemDetails *>( item ) )
578 {
579 if ( annotationItem->itemId() == mSelectedItemId && annotationItem->layerId() == mSelectedItemLayerId )
580 return true;
581 }
582 return false;
583 } );
584 if ( it != items.end() )
585 {
586 const QgsRectangle itemBounds = ( *it )->boundingBox();
587
588 setHoveredItem( dynamic_cast< const QgsRenderedAnnotationItemDetails *>( *it ), itemBounds );
589 if ( !mSelectedRubberBand )
590 createSelectedItemBand();
591
592 mSelectedRubberBand->copyPointsFrom( mHoverRubberBand );
593 mSelectedRubberBand->show();
594 }
595 }
596 mRefreshSelectedItemAfterRedraw = false;
597}
598
599void QgsMapToolModifyAnnotation::setHoveredItem( const QgsRenderedAnnotationItemDetails *item, const QgsRectangle &itemMapBounds )
600{
601 mHoveredItemNodeRubberBands.clear();
602 if ( mHoveredNodeRubberBand )
603 mHoveredNodeRubberBand->hide();
604 mHoveredItemId = item->itemId();
605 mHoveredItemLayerId = item->layerId();
606 if ( !mHoverRubberBand )
607 createHoverBand();
608
609 mHoverRubberBand->show();
610
611 mHoverRubberBand->reset( Qgis::GeometryType::Line );
612 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.xMinimum(), itemMapBounds.yMinimum() ) );
613 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.xMaximum(), itemMapBounds.yMinimum() ) );
614 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.xMaximum(), itemMapBounds.yMaximum() ) );
615 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.xMinimum(), itemMapBounds.yMaximum() ) );
616 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.xMinimum(), itemMapBounds.yMinimum() ) );
617
618 QgsAnnotationLayer *layer = annotationLayerFromId( item->layerId() );
619 const QgsAnnotationItem *annotationItem = annotationItemFromId( item->layerId(), item->itemId() );
620 if ( !annotationItem )
621 return;
622
623 QgsCoordinateTransform layerToMapTransform = QgsCoordinateTransform( layer->crs(), canvas()->mapSettings().destinationCrs(), canvas()->mapSettings().transformContext() );
624
625 const double scaleFactor = canvas()->fontMetrics().xHeight() * .2;
626
627 const QList< QgsAnnotationItemNode > itemNodes = annotationItem->nodes();
629
630 vertexNodeBand->setIcon( QgsRubberBand::ICON_BOX );
631 vertexNodeBand->setWidth( scaleFactor );
632 vertexNodeBand->setIconSize( scaleFactor * 5 );
633 vertexNodeBand->setColor( QColor( 200, 0, 120, 255 ) );
634
635 // store item nodes in a spatial index for quick searching
636 mHoveredItemNodesSpatialIndex = std::make_unique< QgsAnnotationItemNodesSpatialIndex >();
637 int index = 0;
638 mHoveredItemNodes.clear();
639 mHoveredItemNodes.reserve( itemNodes.size() );
640 for ( const QgsAnnotationItemNode &node : itemNodes )
641 {
642 QgsPointXY nodeMapPoint;
643 try
644 {
645 nodeMapPoint = layerToMapTransform.transform( node.point() );
646 }
647 catch ( QgsCsException & )
648 {
649 continue;
650 }
651
652 switch ( node.type() )
653 {
655 vertexNodeBand->addPoint( nodeMapPoint );
656 break;
657 }
658
659 mHoveredItemNodesSpatialIndex->insert( index, QgsRectangle( nodeMapPoint.x(), nodeMapPoint.y(),
660 nodeMapPoint.x(), nodeMapPoint.y() ) );
661
662 QgsAnnotationItemNode transformedNode = node;
663 transformedNode.setPoint( nodeMapPoint );
664 mHoveredItemNodes.append( transformedNode );
665
666 index++;
667 }
668
669 mHoveredItemNodeRubberBands.emplace_back( vertexNodeBand );
670}
671
672QSizeF QgsMapToolModifyAnnotation::deltaForKeyEvent( QgsAnnotationLayer *layer, const QgsPointXY &originalCanvasPoint, QKeyEvent *event )
673{
674 const double canvasDpi = canvas()->window()->windowHandle()->screen()->physicalDotsPerInch();
675
676 // increment used for cursor key item movement
677 double incrementPixels = 0.0;
678 if ( event->modifiers() & Qt::ShiftModifier )
679 {
680 //holding shift while pressing cursor keys results in a big step - 20 mm
681 incrementPixels = 20.0 / 25.4 * canvasDpi;
682 }
683 else if ( event->modifiers() & Qt::AltModifier )
684 {
685 //holding alt while pressing cursor keys results in a 1 pixel step
686 incrementPixels = 1;
687 }
688 else
689 {
690 // 5 mm
691 incrementPixels = 5.0 / 25.4 * canvasDpi;
692 }
693
694 double deltaXPixels = 0;
695 double deltaYPixels = 0;
696 switch ( event->key() )
697 {
698 case Qt::Key_Left:
699 deltaXPixels = -incrementPixels;
700 break;
701 case Qt::Key_Right:
702 deltaXPixels = incrementPixels;
703 break;
704 case Qt::Key_Up:
705 deltaYPixels = -incrementPixels;
706 break;
707 case Qt::Key_Down:
708 deltaYPixels = incrementPixels;
709 break;
710 default:
711 break;
712 }
713
714 const QgsPointXY beforeMoveMapPoint = canvas()->getCoordinateTransform()->toMapCoordinates( originalCanvasPoint.x(), originalCanvasPoint.y() );
715 const QgsPointXY beforeMoveLayerPoint = toLayerCoordinates( layer, beforeMoveMapPoint );
716
717 const QgsPointXY afterMoveCanvasPoint( originalCanvasPoint.x() + deltaXPixels, originalCanvasPoint.y() + deltaYPixels );
718 const QgsPointXY afterMoveMapPoint = canvas()->getCoordinateTransform()->toMapCoordinates( afterMoveCanvasPoint.x(), afterMoveCanvasPoint.y() );
719 const QgsPointXY afterMoveLayerPoint = toLayerCoordinates( layer, afterMoveMapPoint );
720
721 return QSizeF( afterMoveLayerPoint.x() - beforeMoveLayerPoint.x(), afterMoveLayerPoint.y() - beforeMoveLayerPoint.y() );
722}
723
724const QgsRenderedAnnotationItemDetails *QgsMapToolModifyAnnotation::findClosestItemToPoint( const QgsPointXY &mapPoint, const QList<const QgsRenderedAnnotationItemDetails *> &items, QgsRectangle &bounds )
725{
726 const QgsRenderedAnnotationItemDetails *closestItem = nullptr;
727 double closestItemDistance = std::numeric_limits< double >::max();
728 int closestItemZ = 0;
729
730 for ( const QgsRenderedAnnotationItemDetails *item : items )
731 {
732 const QgsAnnotationItem *annotationItem = annotationItemFromId( item->layerId(), item->itemId() );
733 if ( !annotationItem )
734 continue;
735
736 const QgsRectangle itemBounds = item->boundingBox();
737 const double itemDistance = itemBounds.contains( mapPoint ) ? 0 : itemBounds.distance( mapPoint );
738 if ( !closestItem || itemDistance < closestItemDistance || ( itemDistance == closestItemDistance && annotationItem->zIndex() > closestItemZ ) )
739 {
740 closestItem = item;
741 closestItemDistance = itemDistance;
742 closestItemZ = annotationItem->zIndex();
743 bounds = itemBounds;
744 }
745 }
746 return closestItem;
747}
748
749QgsAnnotationLayer *QgsMapToolModifyAnnotation::annotationLayerFromId( const QString &layerId )
750{
751 QgsAnnotationLayer *layer = qobject_cast< QgsAnnotationLayer * >( QgsProject::instance()->mapLayer( layerId ) );
752 if ( !layer && layerId == QgsProject::instance()->mainAnnotationLayer()->id() )
754 return layer;
755}
756
757QgsAnnotationItem *QgsMapToolModifyAnnotation::annotationItemFromId( const QString &layerId, const QString &itemId )
758{
759 QgsAnnotationLayer *layer = annotationLayerFromId( layerId );
760 return layer ? layer->item( itemId ) : nullptr;
761}
762
763void QgsMapToolModifyAnnotation::clearHoveredItem()
764{
765 if ( mHoverRubberBand )
766 mHoverRubberBand->hide();
767 if ( mHoveredNodeRubberBand )
768 mHoveredNodeRubberBand->hide();
769
770 mHoveredItemId.clear();
771 mHoveredItemLayerId.clear();
772 mHoveredItemNodeRubberBands.clear();
773 mHoveredItemNodesSpatialIndex.reset();
774
775 setCursor( Qt::ArrowCursor );
776}
777
778void QgsMapToolModifyAnnotation::clearSelectedItem()
779{
780 if ( mSelectedRubberBand )
781 mSelectedRubberBand->hide();
782
783 const bool hadSelection = !mSelectedItemId.isEmpty();
784 mSelectedItemId.clear();
785 mSelectedItemLayerId.clear();
786 if ( hadSelection )
787 emit selectionCleared();
788}
789
790void QgsMapToolModifyAnnotation::createHoverBand()
791{
792 const double scaleFactor = canvas()->fontMetrics().xHeight() * .2;
793
794 mHoverRubberBand.reset( new QgsRubberBand( mCanvas, Qgis::GeometryType::Line ) );
795 mHoverRubberBand->setWidth( scaleFactor );
796 mHoverRubberBand->setSecondaryStrokeColor( QColor( 255, 255, 255, 100 ) );
797 mHoverRubberBand->setColor( QColor( 100, 100, 100, 155 ) );
798}
799
800void QgsMapToolModifyAnnotation::createHoveredNodeBand()
801{
802 const double scaleFactor = canvas()->fontMetrics().xHeight() * .2;
803
804 mHoveredNodeRubberBand.reset( new QgsRubberBand( mCanvas, Qgis::GeometryType::Point ) );
805 mHoveredNodeRubberBand->setIcon( QgsRubberBand::ICON_FULL_BOX );
806 mHoveredNodeRubberBand->setWidth( scaleFactor );
807 mHoveredNodeRubberBand->setIconSize( scaleFactor * 5 );
808 mHoveredNodeRubberBand->setColor( QColor( 200, 0, 120, 255 ) );
809}
810
811void QgsMapToolModifyAnnotation::createSelectedItemBand()
812{
813 const double scaleFactor = canvas()->fontMetrics().xHeight() * .2;
814
815 mSelectedRubberBand.reset( new QgsRubberBand( mCanvas, Qgis::GeometryType::Line ) );
816 mSelectedRubberBand->setWidth( scaleFactor );
817 mSelectedRubberBand->setSecondaryStrokeColor( QColor( 255, 255, 255, 100 ) );
818 mSelectedRubberBand->setColor( QColor( 50, 50, 50, 200 ) );
819}
820
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
Transform the point from the source CRS to the destination CRS.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:67
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:93
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:81
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:341
QgsMapCanvas * canvas() const
returns pointer to the tool's map canvas
Definition: qgsmaptool.cpp:221
virtual void setCursor(const QCursor &cursor)
Sets a user defined cursor.
Definition: qgsmaptool.cpp:166
static double searchRadiusMU(const QgsRenderContext &context)
Gets search radius in map units for given context.
Definition: qgsmaptool.cpp:238
A class to represent a 2D point.
Definition: qgspointxy.h:60
double y
Definition: qgspointxy.h:64
Q_GADGET double x
Definition: qgspointxy.h:63
bool isEmpty() const
Returns true if the geometry is empty.
Definition: qgspointxy.h:243
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:481
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:599
A rectangle specified with double values.
Definition: qgsrectangle.h:42
bool contains(const QgsRectangle &rect) const
Returns true when rectangle contains other rectangle.
Definition: qgsrectangle.h:385
double xMinimum() const
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:201
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:211
double xMaximum() const
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:196
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:206
void grow(double delta)
Grows the rectangle in place by the specified amount.
Definition: qgsrectangle.h:307
double distance(const QgsPointXY &point) const
Returns the distance from point to the nearest point on the boundary of the rectangle.
Definition: qgsrectangle.h:456
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:54
void setWidth(int width)
Sets the width of the line.
QgsGeometry asGeometry() const
Returns the rubberband as a Geometry.
void reset(Qgis::GeometryType geometryType=Qgis::GeometryType::Line)
Clears all the geometries in this rubberband.
void setSecondaryStrokeColor(const QColor &color)
Sets a secondary stroke color for the rubberband which will be drawn under the main stroke color.
@ ICON_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 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
Returns the vector's y-component.
Definition: qgsvector.h:152
double x() const
Returns the vector's x-component.
Definition: qgsvector.h:143