QGIS API Documentation 3.39.0-Master (d85f3c2a281)
Loading...
Searching...
No Matches
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 mLastHoverPoint = event->originalPixelPoint();
128 event->snapPoint();
129 mSnapIndicator->setMatch( event->mapPointMatch() );
130
131 const QgsPointXY mapPoint = event->mapPoint();
132
134 QgsAnnotationLayer *layer = annotationLayerFromId( mSelectedItemLayerId );
135 context.setCurrentItemBounds( toLayerCoordinates( layer, mSelectedItemBounds ) );
136 context.setRenderContext( QgsRenderContext::fromMapSettings( canvas()->mapSettings() ) );
137
138 switch ( mCurrentAction )
139 {
140 case Action::NoAction:
141 {
142 setHoveredItemFromPoint( mapPoint );
143 break;
144 }
145
146 case Action::MoveItem:
147 {
148 if ( QgsAnnotationItem *item = annotationItemFromId( mSelectedItemLayerId, mSelectedItemId ) )
149 {
150 const QgsVector delta = toLayerCoordinates( layer, event->mapPoint() ) - mMoveStartPointLayerCrs;
151
152 QgsAnnotationItemEditOperationTranslateItem operation( mSelectedItemId, delta.x(), delta.y(),
153 event->pixelPoint().x() - mMoveStartPointPixels.x(),
154 event->pixelPoint().y() - mMoveStartPointPixels.y() );
155 std::unique_ptr< QgsAnnotationItemEditOperationTransientResults > operationResults( item->transientEditResultsV2( &operation, context ) );
156 if ( operationResults )
157 {
158 mTemporaryRubberBand.reset( new QgsRubberBand( mCanvas, operationResults->representativeGeometry().type() ) );
159 const double scaleFactor = canvas()->fontMetrics().xHeight() * .2;
160 mTemporaryRubberBand->setWidth( scaleFactor );
161 mTemporaryRubberBand->setToGeometry( operationResults->representativeGeometry(), layer->crs() );
162 }
163 else
164 {
165 mTemporaryRubberBand.reset();
166 }
167 }
168 break;
169 }
170
171 case Action::MoveNode:
172 {
173 if ( QgsAnnotationItem *item = annotationItemFromId( mSelectedItemLayerId, mSelectedItemId ) )
174 {
175 const QgsPointXY endPointLayer = toLayerCoordinates( layer, event->mapPoint() );
176 QgsAnnotationItemEditOperationMoveNode operation( mSelectedItemId, mTargetNode.id(), QgsPoint( mTargetNode.point() ), QgsPoint( endPointLayer ),
177 event->pixelPoint().x() - mMoveStartPointPixels.x(),
178 event->pixelPoint().y() - mMoveStartPointPixels.y() );
179 std::unique_ptr< QgsAnnotationItemEditOperationTransientResults > operationResults( item->transientEditResultsV2( &operation, context ) );
180 if ( operationResults )
181 {
182 mTemporaryRubberBand.reset( new QgsRubberBand( mCanvas, operationResults->representativeGeometry().type() ) );
183 const double scaleFactor = canvas()->fontMetrics().xHeight() * .2;
184 mTemporaryRubberBand->setWidth( scaleFactor );
185 mTemporaryRubberBand->setToGeometry( operationResults->representativeGeometry(), layer->crs() );
186 }
187 else
188 {
189 mTemporaryRubberBand.reset();
190 }
191 }
192 break;
193 }
194 }
195}
196
198{
199 QgsAnnotationLayer *layer = annotationLayerFromId( mSelectedItemLayerId );
200
202 context.setCurrentItemBounds( toLayerCoordinates( layer, mSelectedItemBounds ) );
203 context.setRenderContext( QgsRenderContext::fromMapSettings( canvas()->mapSettings() ) );
204
205 switch ( mCurrentAction )
206 {
207 case Action::NoAction:
208 {
209 if ( event->button() != Qt::LeftButton )
210 return;
211
212 if ( mHoveredItemId.isEmpty() || !mHoverRubberBand )
213 {
214 clearSelectedItem();
215 }
216 else if ( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId )
217 {
218 // press is on selected item => move that item
219 if ( layer )
220 {
221 const QgsPointXY mapPoint = event->mapPoint();
222 QgsRectangle searchRect = QgsRectangle( mapPoint.x(), mapPoint.y(), mapPoint.x(), mapPoint.y() );
223 searchRect.grow( searchRadiusMU( canvas() ) );
224
225 QgsAnnotationItemNode hoveredNode;
226 double currentNodeDistance = std::numeric_limits< double >::max();
227 mHoveredItemNodesSpatialIndex->intersects( searchRect, [&hoveredNode, &currentNodeDistance, &mapPoint, this]( int index )-> bool
228 {
229 const QgsAnnotationItemNode &thisNode = mHoveredItemNodes.at( index );
230 const double nodeDistance = thisNode.point().sqrDist( mapPoint );
231 if ( nodeDistance < currentNodeDistance )
232 {
233 hoveredNode = thisNode;
234 currentNodeDistance = nodeDistance;
235 }
236 return true;
237 } );
238
239 mMoveStartPointCanvasCrs = mapPoint;
240 mMoveStartPointPixels = event->pixelPoint();
241 mMoveStartPointLayerCrs = toLayerCoordinates( layer, mMoveStartPointCanvasCrs );
242 if ( mHoverRubberBand )
243 mHoverRubberBand->hide();
244 if ( mSelectedRubberBand )
245 mSelectedRubberBand->hide();
246
247 if ( hoveredNode.point().isEmpty() )
248 {
249 mCurrentAction = Action::MoveItem;
250 }
251 else
252 {
253 mCurrentAction = Action::MoveNode;
254 mTargetNode = hoveredNode;
255 }
256 }
257 }
258 else
259 {
260 // press is on a different item to selected item => select that item
261 mSelectedItemId = mHoveredItemId;
262 mSelectedItemLayerId = mHoveredItemLayerId;
263 mSelectedItemBounds = mHoveredItemBounds;
264
265 if ( !mSelectedRubberBand )
266 createSelectedItemBand();
267
268 mSelectedRubberBand->copyPointsFrom( mHoverRubberBand );
269 mSelectedRubberBand->show();
270
271 setCursor( Qt::OpenHandCursor );
272
273 emit itemSelected( annotationLayerFromId( mSelectedItemLayerId ), mSelectedItemId );
274 }
275 break;
276 }
277
278 case Action::MoveItem:
279 {
280 if ( event->button() == Qt::RightButton )
281 {
282 mCurrentAction = Action::NoAction;
283 mTemporaryRubberBand.reset();
284 if ( mSelectedRubberBand )
285 {
286 mSelectedRubberBand->setTranslationOffset( 0, 0 );
287 mSelectedRubberBand->show();
288 }
289 mHoveredItemNodeRubberBands.clear();
290 setCursor( Qt::ArrowCursor );
291 }
292 else if ( event->button() == Qt::LeftButton )
293 {
294 // apply move
295 if ( layer )
296 {
297 const QgsVector delta = toLayerCoordinates( layer, event->mapPoint() ) - mMoveStartPointLayerCrs;
298
299 QgsAnnotationItemEditOperationTranslateItem operation( mSelectedItemId, delta.x(), delta.y(),
300 event->pixelPoint().x() - mMoveStartPointPixels.x(),
301 event->pixelPoint().y() - mMoveStartPointPixels.y() );
302 switch ( layer->applyEditV2( &operation, context ) )
303 {
306 mRefreshSelectedItemAfterRedraw = true;
307 break;
310 break;
311 }
312 }
313
314 mTemporaryRubberBand.reset();
315 mCurrentAction = Action::NoAction;
316 setCursor( Qt::ArrowCursor );
317 }
318 break;
319 }
320
321 case Action::MoveNode:
322 {
323 if ( event->button() == Qt::RightButton )
324 {
325 mCurrentAction = Action::NoAction;
326 mTemporaryRubberBand.reset();
327 mHoveredItemNodeRubberBands.clear();
328 mTemporaryRubberBand.reset();
329 setCursor( Qt::ArrowCursor );
330 }
331 else if ( event->button() == Qt::LeftButton )
332 {
333 if ( layer )
334 {
335 const QgsPointXY endPointLayer = toLayerCoordinates( layer, event->mapPoint() );
336 QgsAnnotationItemEditOperationMoveNode operation( mSelectedItemId, mTargetNode.id(), QgsPoint( mTargetNode.point() ), QgsPoint( endPointLayer ),
337 event->pixelPoint().x() - mMoveStartPointPixels.x(),
338 event->pixelPoint().y() - mMoveStartPointPixels.y() );
339 switch ( layer->applyEditV2( &operation, context ) )
340 {
343 mRefreshSelectedItemAfterRedraw = true;
344 break;
345
348 break;
349 }
350 }
351
352 mTemporaryRubberBand.reset();
353 mHoveredItemNodeRubberBands.clear();
354 mHoveredItemNodes.clear();
355 mTemporaryRubberBand.reset();
356 mCurrentAction = Action::NoAction;
357 setCursor( Qt::ArrowCursor );
358 }
359 break;
360 }
361 }
362}
363
365{
366 switch ( mCurrentAction )
367 {
368 case Action::NoAction:
369 case Action::MoveItem:
370 {
371 if ( event->button() != Qt::LeftButton )
372 return;
373
374 mCurrentAction = Action::NoAction;
375 if ( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId )
376 {
377 // double-click on selected item => add node
378 if ( QgsAnnotationLayer *layer = annotationLayerFromId( mSelectedItemLayerId ) )
379 {
380 const QgsPointXY layerPoint = toLayerCoordinates( layer, event->mapPoint() );
381 QgsAnnotationItemEditOperationAddNode operation( mSelectedItemId, QgsPoint( layerPoint ) );
383 context.setCurrentItemBounds( toLayerCoordinates( layer, mSelectedItemBounds ) );
384 context.setRenderContext( QgsRenderContext::fromMapSettings( canvas()->mapSettings() ) );
385
386 switch ( layer->applyEditV2( &operation, context ) )
387 {
390 mRefreshSelectedItemAfterRedraw = true;
391 break;
392
395 break;
396 }
397 }
398 }
399 else
400 {
401 // press is on a different item to selected item => select that item
402 mSelectedItemId = mHoveredItemId;
403 mSelectedItemLayerId = mHoveredItemLayerId;
404 mSelectedItemBounds = mHoveredItemBounds;
405
406 if ( !mSelectedRubberBand )
407 createSelectedItemBand();
408
409 mSelectedRubberBand->copyPointsFrom( mHoverRubberBand );
410 mSelectedRubberBand->show();
411
412 setCursor( Qt::OpenHandCursor );
413
414 emit itemSelected( annotationLayerFromId( mSelectedItemLayerId ), mSelectedItemId );
415 }
416 break;
417 }
418
419 case Action::MoveNode:
420 break;
421 }
422}
423
425{
427 QgsAnnotationLayer *layer = annotationLayerFromId( mSelectedItemLayerId );
428 context.setCurrentItemBounds( toLayerCoordinates( layer, mSelectedItemBounds ) );
429 context.setRenderContext( QgsRenderContext::fromMapSettings( canvas()->mapSettings() ) );
430
431 switch ( mCurrentAction )
432 {
433 case Action::NoAction:
434 {
435 if ( event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete )
436 {
437
438 if ( !layer || mSelectedItemId.isEmpty() )
439 return;
440
441 layer->removeItem( mSelectedItemId );
442 clearSelectedItem();
443 clearHoveredItem();
444 event->ignore(); // disable default shortcut handling
445 }
446 else if ( event->key() == Qt::Key_Left
447 || event->key() == Qt::Key_Right
448 || event->key() == Qt::Key_Up
449 || event->key() == Qt::Key_Down )
450 {
451 if ( !layer )
452 return;
453
454 const QSizeF deltaLayerCoordinates = deltaForKeyEvent( layer, mSelectedRubberBand->asGeometry().centroid().asPoint(), event );
455
456 QgsAnnotationItemEditOperationTranslateItem operation( mSelectedItemId, deltaLayerCoordinates.width(), deltaLayerCoordinates.height() );
457 switch ( layer->applyEditV2( &operation, context ) )
458 {
461 mRefreshSelectedItemAfterRedraw = true;
462 break;
465 break;
466 }
467 event->ignore(); // disable default shortcut handling (move map)
468 }
469 break;
470 }
471
472 case Action::MoveNode:
473 {
474 if ( event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace )
475 {
476 if ( layer )
477 {
478 QgsAnnotationItemEditOperationDeleteNode operation( mSelectedItemId, mTargetNode.id(), QgsPoint( mTargetNode.point() ) );
479 switch ( layer->applyEditV2( &operation, context ) )
480 {
483 mRefreshSelectedItemAfterRedraw = true;
484 break;
486 break;
489 break;
490 }
491 }
492
493 mTemporaryRubberBand.reset();
494 mHoveredItemNodeRubberBands.clear();
495 mHoveredItemNodes.clear();
496 mTemporaryRubberBand.reset();
497 mCurrentAction = Action::NoAction;
498 setCursor( Qt::ArrowCursor );
499 event->ignore(); // disable default shortcut handling (delete vector feature)
500 break;
501 }
502 [[fallthrough]];
503 }
504
505 case Action::MoveItem:
506 {
507 // warning -- fallthrough above!
508 if ( event->key() == Qt::Key_Escape )
509 {
510 mCurrentAction = Action::NoAction;
511 mTemporaryRubberBand.reset();
512 if ( mSelectedRubberBand )
513 {
514 mSelectedRubberBand->setTranslationOffset( 0, 0 );
515 mSelectedRubberBand->show();
516 }
517 mHoveredItemNodeRubberBands.clear();
518
519 setCursor( Qt::ArrowCursor );
520 }
521 break;
522 }
523 }
524}
525
526void QgsMapToolModifyAnnotation::onCanvasRefreshed()
527{
528 bool needsSelectedItemRefresh = mRefreshSelectedItemAfterRedraw;
529 if ( QgsAnnotationItem *item = annotationItemFromId( mSelectedItemLayerId, mSelectedItemId ) )
530 {
532 {
533 needsSelectedItemRefresh = true;
534 }
535 }
536
537 if ( needsSelectedItemRefresh )
538 {
539 const QgsRenderedItemResults *renderedItemResults = canvas()->renderedItemResults( false );
540 if ( !renderedItemResults )
541 {
542 return;
543 }
544
545 const QList<QgsRenderedItemDetails *> items = renderedItemResults->renderedItems();
546 auto it = std::find_if( items.begin(), items.end(), [this]( const QgsRenderedItemDetails * item )
547 {
548 if ( const QgsRenderedAnnotationItemDetails *annotationItem = dynamic_cast< const QgsRenderedAnnotationItemDetails *>( item ) )
549 {
550 if ( annotationItem->itemId() == mSelectedItemId && annotationItem->layerId() == mSelectedItemLayerId )
551 return true;
552 }
553 return false;
554 } );
555 if ( it != items.end() )
556 {
557 const QgsRectangle itemBounds = ( *it )->boundingBox();
558
559 setHoveredItem( dynamic_cast< const QgsRenderedAnnotationItemDetails *>( *it ), itemBounds );
560 if ( !mSelectedRubberBand )
561 createSelectedItemBand();
562
563 mSelectedRubberBand->copyPointsFrom( mHoverRubberBand );
564 mSelectedRubberBand->show();
565 mSelectedItemBounds = mHoveredItemBounds;
566 }
567 }
568 else
569 {
570 // recheck for hovered item at new mouse point
571 const QgsPointXY mapPoint = canvas()->mapSettings().mapToPixel().toMapCoordinates( mLastHoverPoint );
572 setHoveredItemFromPoint( mapPoint );
573 }
574 mRefreshSelectedItemAfterRedraw = false;
575}
576
577void QgsMapToolModifyAnnotation::setHoveredItem( const QgsRenderedAnnotationItemDetails *item, const QgsRectangle &itemMapBounds )
578{
579 mHoveredItemNodeRubberBands.clear();
580 if ( mHoveredNodeRubberBand )
581 mHoveredNodeRubberBand->hide();
582 mHoveredItemId = item->itemId();
583 mHoveredItemLayerId = item->layerId();
584 mHoveredItemBounds = itemMapBounds;
585 if ( !mHoverRubberBand )
586 createHoverBand();
587
588 mHoverRubberBand->show();
589
590 mHoverRubberBand->reset( Qgis::GeometryType::Line );
591 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.xMinimum(), itemMapBounds.yMinimum() ) );
592 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.xMaximum(), itemMapBounds.yMinimum() ) );
593 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.xMaximum(), itemMapBounds.yMaximum() ) );
594 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.xMinimum(), itemMapBounds.yMaximum() ) );
595 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.xMinimum(), itemMapBounds.yMinimum() ) );
596
597 QgsAnnotationLayer *layer = annotationLayerFromId( item->layerId() );
598 const QgsAnnotationItem *annotationItem = annotationItemFromId( item->layerId(), item->itemId() );
599 if ( !annotationItem )
600 return;
601
602 QgsCoordinateTransform layerToMapTransform = QgsCoordinateTransform( layer->crs(), canvas()->mapSettings().destinationCrs(), canvas()->mapSettings().transformContext() );
603
604 const double scaleFactor = canvas()->fontMetrics().xHeight() * .2;
605
607 context.setCurrentItemBounds( toLayerCoordinates( layer, itemMapBounds ) );
608 context.setRenderContext( QgsRenderContext::fromMapSettings( canvas()->mapSettings() ) );
609
610 const QList< QgsAnnotationItemNode > itemNodes = annotationItem->nodesV2( context );
612
613 vertexNodeBand->setIcon( QgsRubberBand::ICON_BOX );
614 vertexNodeBand->setWidth( scaleFactor );
615 vertexNodeBand->setIconSize( scaleFactor * 5 );
616 vertexNodeBand->setColor( QColor( 200, 0, 120, 255 ) );
617
619 calloutNodeBand->setWidth( scaleFactor );
620 calloutNodeBand->setSecondaryStrokeColor( QColor( 255, 255, 255, 100 ) );
621 calloutNodeBand->setColor( QColor( 120, 200, 0, 255 ) );
622 calloutNodeBand->setIcon( QgsRubberBand::ICON_X );
623 calloutNodeBand->setIconSize( scaleFactor * 5 );
624
625 // store item nodes in a spatial index for quick searching
626 mHoveredItemNodesSpatialIndex = std::make_unique< QgsAnnotationItemNodesSpatialIndex >();
627 int index = 0;
628 mHoveredItemNodes.clear();
629 mHoveredItemNodes.reserve( itemNodes.size() );
630 for ( const QgsAnnotationItemNode &node : itemNodes )
631 {
632 QgsPointXY nodeMapPoint;
633 try
634 {
635 nodeMapPoint = layerToMapTransform.transform( node.point() );
636 }
637 catch ( QgsCsException & )
638 {
639 continue;
640 }
641
642 switch ( node.type() )
643 {
645 vertexNodeBand->addPoint( nodeMapPoint );
646 break;
647
649 calloutNodeBand->addPoint( nodeMapPoint );
650 break;
651 }
652
653 mHoveredItemNodesSpatialIndex->insert( index, QgsRectangle( nodeMapPoint.x(), nodeMapPoint.y(),
654 nodeMapPoint.x(), nodeMapPoint.y() ) );
655
656 QgsAnnotationItemNode transformedNode = node;
657 transformedNode.setPoint( nodeMapPoint );
658 mHoveredItemNodes.append( transformedNode );
659
660 index++;
661 }
662
663 mHoveredItemNodeRubberBands.emplace_back( vertexNodeBand );
664 mHoveredItemNodeRubberBands.emplace_back( calloutNodeBand );
665}
666
667QSizeF QgsMapToolModifyAnnotation::deltaForKeyEvent( QgsAnnotationLayer *layer, const QgsPointXY &originalCanvasPoint, QKeyEvent *event )
668{
669 const double canvasDpi = canvas()->window()->windowHandle()->screen()->physicalDotsPerInch();
670
671 // increment used for cursor key item movement
672 double incrementPixels = 0.0;
673 if ( event->modifiers() & Qt::ShiftModifier )
674 {
675 //holding shift while pressing cursor keys results in a big step - 20 mm
676 incrementPixels = 20.0 / 25.4 * canvasDpi;
677 }
678 else if ( event->modifiers() & Qt::AltModifier )
679 {
680 //holding alt while pressing cursor keys results in a 1 pixel step
681 incrementPixels = 1;
682 }
683 else
684 {
685 // 5 mm
686 incrementPixels = 5.0 / 25.4 * canvasDpi;
687 }
688
689 double deltaXPixels = 0;
690 double deltaYPixels = 0;
691 switch ( event->key() )
692 {
693 case Qt::Key_Left:
694 deltaXPixels = -incrementPixels;
695 break;
696 case Qt::Key_Right:
697 deltaXPixels = incrementPixels;
698 break;
699 case Qt::Key_Up:
700 deltaYPixels = -incrementPixels;
701 break;
702 case Qt::Key_Down:
703 deltaYPixels = incrementPixels;
704 break;
705 default:
706 break;
707 }
708
709 const QgsPointXY beforeMoveMapPoint = canvas()->getCoordinateTransform()->toMapCoordinates( originalCanvasPoint.x(), originalCanvasPoint.y() );
710 const QgsPointXY beforeMoveLayerPoint = toLayerCoordinates( layer, beforeMoveMapPoint );
711
712 const QgsPointXY afterMoveCanvasPoint( originalCanvasPoint.x() + deltaXPixels, originalCanvasPoint.y() + deltaYPixels );
713 const QgsPointXY afterMoveMapPoint = canvas()->getCoordinateTransform()->toMapCoordinates( afterMoveCanvasPoint.x(), afterMoveCanvasPoint.y() );
714 const QgsPointXY afterMoveLayerPoint = toLayerCoordinates( layer, afterMoveMapPoint );
715
716 return QSizeF( afterMoveLayerPoint.x() - beforeMoveLayerPoint.x(), afterMoveLayerPoint.y() - beforeMoveLayerPoint.y() );
717}
718
719const QgsRenderedAnnotationItemDetails *QgsMapToolModifyAnnotation::findClosestItemToPoint( const QgsPointXY &mapPoint, const QList<const QgsRenderedAnnotationItemDetails *> &items, QgsRectangle &bounds )
720{
721 const QgsRenderedAnnotationItemDetails *closestItem = nullptr;
722 double closestItemDistance = std::numeric_limits< double >::max();
723 double closestItemArea = std::numeric_limits< double >::max();
724
725 for ( const QgsRenderedAnnotationItemDetails *item : items )
726 {
727 const QgsAnnotationItem *annotationItem = annotationItemFromId( item->layerId(), item->itemId() );
728 if ( !annotationItem )
729 continue;
730
731 const QgsRectangle itemBounds = item->boundingBox();
732 const double itemDistance = itemBounds.contains( mapPoint ) ? 0 : itemBounds.distance( mapPoint );
733 if ( !closestItem || itemDistance < closestItemDistance || ( itemDistance == closestItemDistance && itemBounds.area() < closestItemArea ) )
734 {
735 closestItem = item;
736 closestItemDistance = itemDistance;
737 closestItemArea = itemBounds.area();
738 bounds = itemBounds;
739 }
740 }
741 return closestItem;
742}
743
744QgsAnnotationLayer *QgsMapToolModifyAnnotation::annotationLayerFromId( const QString &layerId )
745{
746 QgsAnnotationLayer *layer = qobject_cast< QgsAnnotationLayer * >( QgsProject::instance()->mapLayer( layerId ) );
747 if ( !layer && layerId == QgsProject::instance()->mainAnnotationLayer()->id() )
749 return layer;
750}
751
752QgsAnnotationItem *QgsMapToolModifyAnnotation::annotationItemFromId( const QString &layerId, const QString &itemId )
753{
754 QgsAnnotationLayer *layer = annotationLayerFromId( layerId );
755 return layer ? layer->item( itemId ) : nullptr;
756}
757
758void QgsMapToolModifyAnnotation::setHoveredItemFromPoint( const QgsPointXY &mapPoint )
759{
760 QgsRectangle searchRect = QgsRectangle( mapPoint.x(), mapPoint.y(), mapPoint.x(), mapPoint.y() );
761 searchRect.grow( searchRadiusMU( canvas() ) );
762
763 const QgsRenderedItemResults *renderedItemResults = canvas()->renderedItemResults( false );
764 if ( !renderedItemResults )
765 {
766 clearHoveredItem();
767 return;
768 }
769
770 const QList<const QgsRenderedAnnotationItemDetails *> items = renderedItemResults->renderedAnnotationItemsInBounds( searchRect );
771 if ( items.empty() )
772 {
773 clearHoveredItem();
774 return;
775 }
776
777 // find closest item
778 QgsRectangle itemBounds;
779 const QgsRenderedAnnotationItemDetails *closestItem = findClosestItemToPoint( mapPoint, items, itemBounds );
780 if ( !closestItem )
781 {
782 clearHoveredItem();
783 return;
784 }
785
786 if ( closestItem->itemId() != mHoveredItemId || closestItem->layerId() != mHoveredItemLayerId )
787 {
788 setHoveredItem( closestItem, itemBounds );
789 }
790
791 // track hovered node too!... here we want to identify the closest node to the cursor position
792 QgsAnnotationItemNode hoveredNode;
793 if ( closestItem->itemId() == mSelectedItemId && closestItem->layerId() == mSelectedItemLayerId )
794 {
795 double currentNodeDistance = std::numeric_limits< double >::max();
796 mHoveredItemNodesSpatialIndex->intersects( searchRect, [&hoveredNode, &currentNodeDistance, &mapPoint, this]( int index )-> bool
797 {
798 if ( index >= mHoveredItemNodes.size() )
799 return false;
800
801 const QgsAnnotationItemNode &thisNode = mHoveredItemNodes.at( index );
802 const double nodeDistance = thisNode.point().sqrDist( mapPoint );
803 if ( nodeDistance < currentNodeDistance )
804 {
805 hoveredNode = thisNode;
806 currentNodeDistance = nodeDistance;
807 }
808 return true;
809 } );
810 }
811
812 if ( hoveredNode.point().isEmpty() )
813 {
814 // no hovered node
815 if ( mHoveredNodeRubberBand )
816 mHoveredNodeRubberBand->hide();
817 setCursor( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId ? Qt::OpenHandCursor : Qt::ArrowCursor );
818 }
819 else
820 {
821 if ( !mHoveredNodeRubberBand )
822 createHoveredNodeBand();
823
824 mHoveredNodeRubberBand->reset( Qgis::GeometryType::Point );
825 mHoveredNodeRubberBand->addPoint( hoveredNode.point() );
826 mHoveredNodeRubberBand->show();
827
828 setCursor( hoveredNode.cursor() );
829 }
830}
831
832void QgsMapToolModifyAnnotation::clearHoveredItem()
833{
834 if ( mHoverRubberBand )
835 mHoverRubberBand->hide();
836 if ( mHoveredNodeRubberBand )
837 mHoveredNodeRubberBand->hide();
838
839 mHoveredItemId.clear();
840 mHoveredItemLayerId.clear();
841 mHoveredItemNodeRubberBands.clear();
842 mHoveredItemNodesSpatialIndex.reset();
843
844 setCursor( Qt::ArrowCursor );
845}
846
847void QgsMapToolModifyAnnotation::clearSelectedItem()
848{
849 if ( mSelectedRubberBand )
850 mSelectedRubberBand->hide();
851
852 const bool hadSelection = !mSelectedItemId.isEmpty();
853 mSelectedItemId.clear();
854 mSelectedItemLayerId.clear();
855 if ( hadSelection )
856 emit selectionCleared();
857}
858
859void QgsMapToolModifyAnnotation::createHoverBand()
860{
861 const double scaleFactor = canvas()->fontMetrics().xHeight() * .2;
862
863 mHoverRubberBand.reset( new QgsRubberBand( mCanvas, Qgis::GeometryType::Line ) );
864 mHoverRubberBand->setWidth( scaleFactor );
865 mHoverRubberBand->setSecondaryStrokeColor( QColor( 255, 255, 255, 100 ) );
866 mHoverRubberBand->setColor( QColor( 100, 100, 100, 155 ) );
867}
868
869void QgsMapToolModifyAnnotation::createHoveredNodeBand()
870{
871 const double scaleFactor = canvas()->fontMetrics().xHeight() * .2;
872
873 mHoveredNodeRubberBand.reset( new QgsRubberBand( mCanvas, Qgis::GeometryType::Point ) );
874 mHoveredNodeRubberBand->setIcon( QgsRubberBand::ICON_FULL_BOX );
875 mHoveredNodeRubberBand->setWidth( scaleFactor );
876 mHoveredNodeRubberBand->setIconSize( scaleFactor * 5 );
877 mHoveredNodeRubberBand->setColor( QColor( 200, 0, 120, 255 ) );
878}
879
880void QgsMapToolModifyAnnotation::createSelectedItemBand()
881{
882 const double scaleFactor = canvas()->fontMetrics().xHeight() * .2;
883
884 mSelectedRubberBand.reset( new QgsRubberBand( mCanvas, Qgis::GeometryType::Line ) );
885 mSelectedRubberBand->setWidth( scaleFactor );
886 mSelectedRubberBand->setSecondaryStrokeColor( QColor( 255, 255, 255, 100 ) );
887 mSelectedRubberBand->setColor( QColor( 50, 50, 50, 200 ) );
888}
889
void reset(T *p=nullptr)
Will reset the managed pointer to p.
@ VertexHandle
Node is a handle for manipulating vertices.
@ CalloutHandle
Node is a handle for manipulating callouts.
@ ScaleDependentBoundingBox
Item's bounding box will vary depending on map scale.
@ Invalid
Operation has invalid parameters for the item, no change occurred.
@ Success
Item was modified successfully.
@ ItemCleared
The operation results in the item being cleared, and the item should be removed from the layer as a r...
The QgsAdvancedDigitizingDockWidget class is a dockable widget used to handle the CAD tools on top of...
Encapsulates the context for an annotation item edit operation.
void setCurrentItemBounds(const QgsRectangle &bounds)
Sets the current rendered bounds of the item, in the annotation layer's CRS.
void setRenderContext(const QgsRenderContext &context)
Sets the render context associated with the edit operation.
Annotation item edit operation consisting of adding a node.
Annotation item edit operation consisting of deleting a node.
Annotation item edit operation consisting of moving a node.
Annotation item edit operation consisting of translating (moving) an item.
Contains information about a node used for editing an annotation item.
void setPoint(QgsPointXY point)
Sets the node's position, in geographic coordinates.
QgsPointXY point() const
Returns the node's position, in geographic coordinates.
Qt::CursorShape cursor() const
Returns the mouse cursor shape to use when hovering the node.
QgsVertexId id() const
Returns the ID number of the node, used for uniquely identifying the node in the item.
Abstract base class for annotation items which are drawn with QgsAnnotationLayers.
virtual QList< QgsAnnotationItemNode > nodesV2(const QgsAnnotationItemEditContext &context) const
Returns the nodes for the item, used for editing the item.
Represents a map layer containing a set of georeferenced annotations, e.g.
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.
QgsPointXY asPoint() const
Returns the contents of the geometry as a 2-dimensional point.
QgsGeometry centroid() const
Returns the center of mass of a geometry.
Map canvas is a class for displaying all GIS data types on a canvas.
const QgsRenderedItemResults * renderedItemResults(bool allowOutdatedResults=true) const
Gets access to the rendered item results (may be nullptr), which includes the results of rendering an...
void mapCanvasRefreshed()
Emitted when canvas finished a refresh request.
const QgsMapToPixel * getCoordinateTransform()
Gets the current coordinate transform.
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:83
A QgsMapMouseEvent is the result of a user interaction with the mouse on a QgsMapCanvas.
QgsPointXY mapPoint() const
mapPoint returns the point in coordinates
QPoint pixelPoint() const
The snapped mouse cursor in pixel coordinates.
QgsPointLocator::Match mapPointMatch() const
Returns the matching data from the most recently snapped point.
QgsPointXY toMapCoordinates(int x, int y) const
Transforms device coordinates to map (world) coordinates.
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.
QPointer< QgsMapCanvas > mCanvas
The pointer to the map canvas.
Definition qgsmaptool.h:341
QgsMapCanvas * canvas() const
returns pointer to the tool's map canvas
virtual void setCursor(const QCursor &cursor)
Sets a user defined cursor.
static double searchRadiusMU(const QgsRenderContext &context)
Gets search radius in map units for given context.
A class to represent a 2D point.
Definition qgspointxy.h:60
double sqrDist(double x, double y) const
Returns the squared distance between this point a specified x, y coordinate.
Definition qgspointxy.h:186
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
bool isEmpty() const
Returns true if the geometry is empty.
Definition qgspointxy.h:242
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
static QgsProject * instance()
Returns the QgsProject singleton instance.
QgsAnnotationLayer * mainAnnotationLayer()
Returns the main annotation layer associated with the project.
void setDirty(bool b=true)
Flag the project as dirty (modified).
A rectangle specified with double values.
bool contains(const QgsRectangle &rect) const
Returns true when rectangle contains other rectangle.
double area() const
Returns the area of the rectangle.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
double xMaximum() const
Returns the x maximum value (right side of rectangle).
double yMaximum() const
Returns the y maximum value (top side of rectangle).
void grow(double delta)
Grows the rectangle in place by the specified amount.
double distance(const QgsPointXY &point) const
Returns the distance from point to the nearest point on the boundary of the rectangle.
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
Contains information about a rendered annotation item.
QString itemId() const
Returns the item ID of the associated annotation item.
Base class for detailed information about a rendered item.
QString layerId() const
Returns the layer ID of the associated map layer.
QgsRectangle boundingBox() const
Returns the bounding box of the item (in map units).
Stores collated details of rendered items during a map rendering operation.
QList< const QgsRenderedAnnotationItemDetails * > renderedAnnotationItemsInBounds(const QgsRectangle &bounds) const
Returns a list with details of the rendered annotation items within the specified bounds.
QList< QgsRenderedItemDetails * > renderedItems() const
Returns a list of all rendered items.
A class for drawing transient features (e.g.
void setIconSize(double iconSize)
Sets the size of the point icons.
QgsGeometry asGeometry() const
Returns the rubberband as a Geometry.
void setWidth(double width)
Sets the width of the line.
void reset(Qgis::GeometryType geometryType=Qgis::GeometryType::Line)
Clears all the geometries in this rubberband.
void setSecondaryStrokeColor(const QColor &color)
Sets a secondary stroke color for the rubberband which will be drawn under the main stroke color.
@ ICON_X
A cross is used to highlight points (x)
@ ICON_FULL_BOX
A full box is used to highlight points (■)
@ ICON_BOX
A box is used to highlight points (□)
void setColor(const QColor &color)
Sets the color for the rubberband.
void setToGeometry(const QgsGeometry &geom, QgsVectorLayer *layer)
Sets this rubber band to geom.
void setIcon(IconType icon)
Sets the icon type to highlight point geometries.
void copyPointsFrom(const QgsRubberBand *other)
Copies the points from another rubber band.
void setTranslationOffset(double dx, double dy)
Adds translation to original coordinates (all in map coordinates)
void addPoint(const QgsPointXY &p, bool doUpdate=true, int geometryIndex=0, int ringIndex=0)
Adds a vertex to the rubberband and update canvas.
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