QGIS API Documentation 3.41.0-Master (57ec4277f5e)
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 "moc_qgsmaptoolmodifyannotation.cpp"
19#include "qgsrubberband.h"
20#include "qgsmapmouseevent.h"
21#include "qgsmapcanvas.h"
24#include "qgsannotationlayer.h"
25#include "qgsproject.h"
27#include "qgsannotationitem.h"
30#include "qgssnapindicator.h"
31#include "RTree.h"
32#include <QTransform>
33#include <QWindow>
34#include <QScreen>
35
37class QgsAnnotationItemNodesSpatialIndex : public RTree<int, float, 2, float>
38{
39 public:
40 void insert( int index, const QgsRectangle &bounds )
41 {
42 std::array<float, 4> scaledBounds = scaleBounds( bounds );
43 this->Insert(
44 { scaledBounds[0], scaledBounds[1]
45 },
46 { scaledBounds[2], scaledBounds[3]
47 },
48 index
49 );
50 }
51
58 void remove( int index, const QgsRectangle &bounds )
59 {
60 std::array<float, 4> scaledBounds = scaleBounds( bounds );
61 this->Remove(
62 { scaledBounds[0], scaledBounds[1]
63 },
64 { scaledBounds[2], scaledBounds[3]
65 },
66 index
67 );
68 }
69
75 bool intersects( const QgsRectangle &bounds, const std::function<bool( int index )> &callback ) const
76 {
77 std::array<float, 4> scaledBounds = scaleBounds( bounds );
78 this->Search(
79 { scaledBounds[0], scaledBounds[1]
80 },
81 { scaledBounds[2], scaledBounds[3]
82 },
83 callback
84 );
85 return true;
86 }
87
88 private:
89 std::array<float, 4> scaleBounds( const QgsRectangle &bounds ) const
90 {
91 return {
92 static_cast<float>( bounds.xMinimum() ),
93 static_cast<float>( bounds.yMinimum() ),
94 static_cast<float>( bounds.xMaximum() ),
95 static_cast<float>( bounds.yMaximum() )
96 };
97 }
98};
100
101
103 : QgsMapToolAdvancedDigitizing( canvas, cadDockWidget )
104 , mSnapIndicator( new QgsSnapIndicator( canvas ) )
105{
106 connect( QgsMapToolModifyAnnotation::canvas(), &QgsMapCanvas::mapCanvasRefreshed, this, &QgsMapToolModifyAnnotation::onCanvasRefreshed );
107}
108
110
112{
113 mSnapIndicator->setMatch( QgsPointLocator::Match() );
114
115 clearHoveredItem();
116 clearSelectedItem();
118}
119
121{
122 mLastHoverPoint = event->originalPixelPoint();
123 event->snapPoint();
124 mSnapIndicator->setMatch( event->mapPointMatch() );
125
126 const QgsPointXY mapPoint = event->mapPoint();
127
129 QgsAnnotationLayer *layer = annotationLayerFromId( mSelectedItemLayerId );
130 context.setCurrentItemBounds( toLayerCoordinates( layer, mSelectedItemBounds ) );
131 context.setRenderContext( QgsRenderContext::fromMapSettings( canvas()->mapSettings() ) );
132
133 switch ( mCurrentAction )
134 {
135 case Action::NoAction:
136 {
137 setHoveredItemFromPoint( mapPoint );
138 break;
139 }
140
141 case Action::MoveItem:
142 {
143 if ( QgsAnnotationItem *item = annotationItemFromId( mSelectedItemLayerId, mSelectedItemId ) )
144 {
145 const QgsVector delta = toLayerCoordinates( layer, event->mapPoint() ) - mMoveStartPointLayerCrs;
146
147 QgsAnnotationItemEditOperationTranslateItem operation( mSelectedItemId, delta.x(), delta.y(), event->pixelPoint().x() - mMoveStartPointPixels.x(), event->pixelPoint().y() - mMoveStartPointPixels.y() );
148 std::unique_ptr<QgsAnnotationItemEditOperationTransientResults> operationResults( item->transientEditResultsV2( &operation, context ) );
149 if ( operationResults )
150 {
151 mTemporaryRubberBand.reset( new QgsRubberBand( mCanvas, operationResults->representativeGeometry().type() ) );
152 const double scaleFactor = canvas()->fontMetrics().xHeight() * .2;
153 mTemporaryRubberBand->setWidth( scaleFactor );
154 mTemporaryRubberBand->setToGeometry( operationResults->representativeGeometry(), layer->crs() );
155 }
156 else
157 {
158 mTemporaryRubberBand.reset();
159 }
160 }
161 break;
162 }
163
164 case Action::MoveNode:
165 {
166 if ( QgsAnnotationItem *item = annotationItemFromId( mSelectedItemLayerId, mSelectedItemId ) )
167 {
168 const QgsPointXY endPointLayer = toLayerCoordinates( layer, event->mapPoint() );
169 QgsAnnotationItemEditOperationMoveNode operation( mSelectedItemId, mTargetNode.id(), QgsPoint( mTargetNode.point() ), QgsPoint( endPointLayer ), event->pixelPoint().x() - mMoveStartPointPixels.x(), event->pixelPoint().y() - mMoveStartPointPixels.y() );
170 std::unique_ptr<QgsAnnotationItemEditOperationTransientResults> operationResults( item->transientEditResultsV2( &operation, context ) );
171 if ( operationResults )
172 {
173 mTemporaryRubberBand.reset( new QgsRubberBand( mCanvas, operationResults->representativeGeometry().type() ) );
174 const double scaleFactor = canvas()->fontMetrics().xHeight() * .2;
175 mTemporaryRubberBand->setWidth( scaleFactor );
176 mTemporaryRubberBand->setToGeometry( operationResults->representativeGeometry(), layer->crs() );
177 }
178 else
179 {
180 mTemporaryRubberBand.reset();
181 }
182 }
183 break;
184 }
185 }
186}
187
189{
190 QgsAnnotationLayer *layer = annotationLayerFromId( mSelectedItemLayerId );
191
193 context.setCurrentItemBounds( toLayerCoordinates( layer, mSelectedItemBounds ) );
194 context.setRenderContext( QgsRenderContext::fromMapSettings( canvas()->mapSettings() ) );
195
196 switch ( mCurrentAction )
197 {
198 case Action::NoAction:
199 {
200 if ( event->button() != Qt::LeftButton )
201 return;
202
203 if ( mHoveredItemId.isEmpty() || !mHoverRubberBand )
204 {
205 clearSelectedItem();
206 }
207 else if ( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId )
208 {
209 // press is on selected item => move that item
210 if ( layer )
211 {
212 const QgsPointXY mapPoint = event->mapPoint();
213 QgsRectangle searchRect = QgsRectangle( mapPoint.x(), mapPoint.y(), mapPoint.x(), mapPoint.y() );
214 searchRect.grow( searchRadiusMU( canvas() ) );
215
216 QgsAnnotationItemNode hoveredNode;
217 double currentNodeDistance = std::numeric_limits<double>::max();
218 mHoveredItemNodesSpatialIndex->intersects( searchRect, [&hoveredNode, &currentNodeDistance, &mapPoint, this]( int index ) -> bool {
219 const QgsAnnotationItemNode &thisNode = mHoveredItemNodes.at( index );
220 const double nodeDistance = thisNode.point().sqrDist( mapPoint );
221 if ( nodeDistance < currentNodeDistance )
222 {
223 hoveredNode = thisNode;
224 currentNodeDistance = nodeDistance;
225 }
226 return true;
227 } );
228
229 mMoveStartPointCanvasCrs = mapPoint;
230 mMoveStartPointPixels = event->pixelPoint();
231 mMoveStartPointLayerCrs = toLayerCoordinates( layer, mMoveStartPointCanvasCrs );
232 if ( mHoverRubberBand )
233 mHoverRubberBand->hide();
234 if ( mSelectedRubberBand )
235 mSelectedRubberBand->hide();
236
237 if ( hoveredNode.point().isEmpty() )
238 {
239 mCurrentAction = Action::MoveItem;
240 }
241 else
242 {
243 mCurrentAction = Action::MoveNode;
244 mTargetNode = hoveredNode;
245 }
246 }
247 }
248 else
249 {
250 // press is on a different item to selected item => select that item
251 mSelectedItemId = mHoveredItemId;
252 mSelectedItemLayerId = mHoveredItemLayerId;
253 mSelectedItemBounds = mHoveredItemBounds;
254
255 if ( !mSelectedRubberBand )
256 createSelectedItemBand();
257
258 mSelectedRubberBand->copyPointsFrom( mHoverRubberBand );
259 mSelectedRubberBand->show();
260
261 setCursor( Qt::OpenHandCursor );
262
263 emit itemSelected( annotationLayerFromId( mSelectedItemLayerId ), mSelectedItemId );
264 }
265 break;
266 }
267
268 case Action::MoveItem:
269 {
270 if ( event->button() == Qt::RightButton )
271 {
272 mCurrentAction = Action::NoAction;
273 mTemporaryRubberBand.reset();
274 if ( mSelectedRubberBand )
275 {
276 mSelectedRubberBand->setTranslationOffset( 0, 0 );
277 mSelectedRubberBand->show();
278 }
279 mHoveredItemNodeRubberBands.clear();
280 setCursor( Qt::ArrowCursor );
281 }
282 else if ( event->button() == Qt::LeftButton )
283 {
284 // apply move
285 if ( layer )
286 {
287 const QgsVector delta = toLayerCoordinates( layer, event->mapPoint() ) - mMoveStartPointLayerCrs;
288
289 QgsAnnotationItemEditOperationTranslateItem operation( mSelectedItemId, delta.x(), delta.y(), event->pixelPoint().x() - mMoveStartPointPixels.x(), event->pixelPoint().y() - mMoveStartPointPixels.y() );
290 switch ( layer->applyEditV2( &operation, context ) )
291 {
294 mRefreshSelectedItemAfterRedraw = true;
295 break;
298 break;
299 }
300 }
301
302 mTemporaryRubberBand.reset();
303 mCurrentAction = Action::NoAction;
304 setCursor( Qt::ArrowCursor );
305 }
306 break;
307 }
308
309 case Action::MoveNode:
310 {
311 if ( event->button() == Qt::RightButton )
312 {
313 mCurrentAction = Action::NoAction;
314 mTemporaryRubberBand.reset();
315 mHoveredItemNodeRubberBands.clear();
316 mTemporaryRubberBand.reset();
317 setCursor( Qt::ArrowCursor );
318 }
319 else if ( event->button() == Qt::LeftButton )
320 {
321 if ( layer )
322 {
323 const QgsPointXY endPointLayer = toLayerCoordinates( layer, event->mapPoint() );
324 QgsAnnotationItemEditOperationMoveNode operation( mSelectedItemId, mTargetNode.id(), QgsPoint( mTargetNode.point() ), QgsPoint( endPointLayer ), event->pixelPoint().x() - mMoveStartPointPixels.x(), event->pixelPoint().y() - mMoveStartPointPixels.y() );
325 switch ( layer->applyEditV2( &operation, context ) )
326 {
329 mRefreshSelectedItemAfterRedraw = true;
330 break;
331
334 break;
335 }
336 }
337
338 mTemporaryRubberBand.reset();
339 mHoveredItemNodeRubberBands.clear();
340 mHoveredItemNodes.clear();
341 mTemporaryRubberBand.reset();
342 mCurrentAction = Action::NoAction;
343 setCursor( Qt::ArrowCursor );
344 }
345 break;
346 }
347 }
348}
349
351{
352 switch ( mCurrentAction )
353 {
354 case Action::NoAction:
355 case Action::MoveItem:
356 {
357 if ( event->button() != Qt::LeftButton )
358 return;
359
360 mCurrentAction = Action::NoAction;
361 if ( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId )
362 {
363 // double-click on selected item => add node
364 if ( QgsAnnotationLayer *layer = annotationLayerFromId( mSelectedItemLayerId ) )
365 {
366 const QgsPointXY layerPoint = toLayerCoordinates( layer, event->mapPoint() );
367 QgsAnnotationItemEditOperationAddNode operation( mSelectedItemId, QgsPoint( layerPoint ) );
369 context.setCurrentItemBounds( toLayerCoordinates( layer, mSelectedItemBounds ) );
370 context.setRenderContext( QgsRenderContext::fromMapSettings( canvas()->mapSettings() ) );
371
372 switch ( layer->applyEditV2( &operation, context ) )
373 {
376 mRefreshSelectedItemAfterRedraw = true;
377 break;
378
381 break;
382 }
383 }
384 }
385 else
386 {
387 // press is on a different item to selected item => select that item
388 mSelectedItemId = mHoveredItemId;
389 mSelectedItemLayerId = mHoveredItemLayerId;
390 mSelectedItemBounds = mHoveredItemBounds;
391
392 if ( !mSelectedRubberBand )
393 createSelectedItemBand();
394
395 mSelectedRubberBand->copyPointsFrom( mHoverRubberBand );
396 mSelectedRubberBand->show();
397
398 setCursor( Qt::OpenHandCursor );
399
400 emit itemSelected( annotationLayerFromId( mSelectedItemLayerId ), mSelectedItemId );
401 }
402 break;
403 }
404
405 case Action::MoveNode:
406 break;
407 }
408}
409
411{
413 QgsAnnotationLayer *layer = annotationLayerFromId( mSelectedItemLayerId );
414 context.setCurrentItemBounds( toLayerCoordinates( layer, mSelectedItemBounds ) );
415 context.setRenderContext( QgsRenderContext::fromMapSettings( canvas()->mapSettings() ) );
416
417 switch ( mCurrentAction )
418 {
419 case Action::NoAction:
420 {
421 if ( event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete )
422 {
423 if ( !layer || mSelectedItemId.isEmpty() )
424 return;
425
426 layer->removeItem( mSelectedItemId );
427 clearSelectedItem();
428 clearHoveredItem();
429 event->ignore(); // disable default shortcut handling
430 }
431 else if ( event->key() == Qt::Key_Left
432 || event->key() == Qt::Key_Right
433 || event->key() == Qt::Key_Up
434 || event->key() == Qt::Key_Down )
435 {
436 if ( !layer )
437 return;
438
439 const QSizeF deltaLayerCoordinates = deltaForKeyEvent( layer, mSelectedRubberBand->asGeometry().centroid().asPoint(), event );
440
441 QgsAnnotationItemEditOperationTranslateItem operation( mSelectedItemId, deltaLayerCoordinates.width(), deltaLayerCoordinates.height() );
442 switch ( layer->applyEditV2( &operation, context ) )
443 {
446 mRefreshSelectedItemAfterRedraw = true;
447 break;
450 break;
451 }
452 event->ignore(); // disable default shortcut handling (move map)
453 }
454 break;
455 }
456
457 case Action::MoveNode:
458 {
459 if ( event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace )
460 {
461 if ( layer )
462 {
463 QgsAnnotationItemEditOperationDeleteNode operation( mSelectedItemId, mTargetNode.id(), QgsPoint( mTargetNode.point() ) );
464 switch ( layer->applyEditV2( &operation, context ) )
465 {
468 mRefreshSelectedItemAfterRedraw = true;
469 break;
471 break;
474 break;
475 }
476 }
477
478 mTemporaryRubberBand.reset();
479 mHoveredItemNodeRubberBands.clear();
480 mHoveredItemNodes.clear();
481 mTemporaryRubberBand.reset();
482 mCurrentAction = Action::NoAction;
483 setCursor( Qt::ArrowCursor );
484 event->ignore(); // disable default shortcut handling (delete vector feature)
485 break;
486 }
487 [[fallthrough]];
488 }
489
490 case Action::MoveItem:
491 {
492 // warning -- fallthrough above!
493 if ( event->key() == Qt::Key_Escape )
494 {
495 mCurrentAction = Action::NoAction;
496 mTemporaryRubberBand.reset();
497 if ( mSelectedRubberBand )
498 {
499 mSelectedRubberBand->setTranslationOffset( 0, 0 );
500 mSelectedRubberBand->show();
501 }
502 mHoveredItemNodeRubberBands.clear();
503
504 setCursor( Qt::ArrowCursor );
505 }
506 break;
507 }
508 }
509}
510
511void QgsMapToolModifyAnnotation::onCanvasRefreshed()
512{
513 bool needsSelectedItemRefresh = mRefreshSelectedItemAfterRedraw;
514 if ( QgsAnnotationItem *item = annotationItemFromId( mSelectedItemLayerId, mSelectedItemId ) )
515 {
517 {
518 needsSelectedItemRefresh = true;
519 }
520 }
521
522 if ( needsSelectedItemRefresh )
523 {
524 const QgsRenderedItemResults *renderedItemResults = canvas()->renderedItemResults( false );
525 if ( !renderedItemResults )
526 {
527 return;
528 }
529
530 const QList<QgsRenderedItemDetails *> items = renderedItemResults->renderedItems();
531 auto it = std::find_if( items.begin(), items.end(), [this]( const QgsRenderedItemDetails *item ) {
532 if ( const QgsRenderedAnnotationItemDetails *annotationItem = dynamic_cast<const QgsRenderedAnnotationItemDetails *>( item ) )
533 {
534 if ( annotationItem->itemId() == mSelectedItemId && annotationItem->layerId() == mSelectedItemLayerId )
535 return true;
536 }
537 return false;
538 } );
539 if ( it != items.end() )
540 {
541 const QgsRectangle itemBounds = ( *it )->boundingBox();
542
543 setHoveredItem( dynamic_cast<const QgsRenderedAnnotationItemDetails *>( *it ), itemBounds );
544 if ( !mSelectedRubberBand )
545 createSelectedItemBand();
546
547 mSelectedRubberBand->copyPointsFrom( mHoverRubberBand );
548 mSelectedRubberBand->show();
549 mSelectedItemBounds = mHoveredItemBounds;
550 }
551 }
552 else
553 {
554 // recheck for hovered item at new mouse point
555 const QgsPointXY mapPoint = canvas()->mapSettings().mapToPixel().toMapCoordinates( mLastHoverPoint );
556 setHoveredItemFromPoint( mapPoint );
557 }
558 mRefreshSelectedItemAfterRedraw = false;
559}
560
561void QgsMapToolModifyAnnotation::setHoveredItem( const QgsRenderedAnnotationItemDetails *item, const QgsRectangle &itemMapBounds )
562{
563 mHoveredItemNodeRubberBands.clear();
564 if ( mHoveredNodeRubberBand )
565 mHoveredNodeRubberBand->hide();
566 mHoveredItemId = item->itemId();
567 mHoveredItemLayerId = item->layerId();
568 mHoveredItemBounds = itemMapBounds;
569 if ( !mHoverRubberBand )
570 createHoverBand();
571
572 mHoverRubberBand->show();
573
574 mHoverRubberBand->reset( Qgis::GeometryType::Line );
575 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.xMinimum(), itemMapBounds.yMinimum() ) );
576 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.xMaximum(), itemMapBounds.yMinimum() ) );
577 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.xMaximum(), itemMapBounds.yMaximum() ) );
578 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.xMinimum(), itemMapBounds.yMaximum() ) );
579 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.xMinimum(), itemMapBounds.yMinimum() ) );
580
581 QgsAnnotationLayer *layer = annotationLayerFromId( item->layerId() );
582 const QgsAnnotationItem *annotationItem = annotationItemFromId( item->layerId(), item->itemId() );
583 if ( !annotationItem )
584 return;
585
586 QgsCoordinateTransform layerToMapTransform = QgsCoordinateTransform( layer->crs(), canvas()->mapSettings().destinationCrs(), canvas()->mapSettings().transformContext() );
587
588 const double scaleFactor = canvas()->fontMetrics().xHeight() * .2;
589
591 context.setCurrentItemBounds( toLayerCoordinates( layer, itemMapBounds ) );
592 context.setRenderContext( QgsRenderContext::fromMapSettings( canvas()->mapSettings() ) );
593
594 const QList<QgsAnnotationItemNode> itemNodes = annotationItem->nodesV2( context );
596
597 vertexNodeBand->setIcon( QgsRubberBand::ICON_BOX );
598 vertexNodeBand->setWidth( scaleFactor );
599 vertexNodeBand->setIconSize( scaleFactor * 5 );
600 vertexNodeBand->setColor( QColor( 200, 0, 120, 255 ) );
601
603 calloutNodeBand->setWidth( scaleFactor );
604 calloutNodeBand->setSecondaryStrokeColor( QColor( 255, 255, 255, 100 ) );
605 calloutNodeBand->setColor( QColor( 120, 200, 0, 255 ) );
606 calloutNodeBand->setIcon( QgsRubberBand::ICON_X );
607 calloutNodeBand->setIconSize( scaleFactor * 5 );
608
609 // store item nodes in a spatial index for quick searching
610 mHoveredItemNodesSpatialIndex = std::make_unique<QgsAnnotationItemNodesSpatialIndex>();
611 int index = 0;
612 mHoveredItemNodes.clear();
613 mHoveredItemNodes.reserve( itemNodes.size() );
614 for ( const QgsAnnotationItemNode &node : itemNodes )
615 {
616 QgsPointXY nodeMapPoint;
617 try
618 {
619 nodeMapPoint = layerToMapTransform.transform( node.point() );
620 }
621 catch ( QgsCsException & )
622 {
623 continue;
624 }
625
626 switch ( node.type() )
627 {
629 vertexNodeBand->addPoint( nodeMapPoint );
630 break;
631
633 calloutNodeBand->addPoint( nodeMapPoint );
634 break;
635 }
636
637 mHoveredItemNodesSpatialIndex->insert( index, QgsRectangle( nodeMapPoint.x(), nodeMapPoint.y(), nodeMapPoint.x(), nodeMapPoint.y() ) );
638
639 QgsAnnotationItemNode transformedNode = node;
640 transformedNode.setPoint( nodeMapPoint );
641 mHoveredItemNodes.append( transformedNode );
642
643 index++;
644 }
645
646 mHoveredItemNodeRubberBands.emplace_back( vertexNodeBand );
647 mHoveredItemNodeRubberBands.emplace_back( calloutNodeBand );
648}
649
650QSizeF QgsMapToolModifyAnnotation::deltaForKeyEvent( QgsAnnotationLayer *layer, const QgsPointXY &originalCanvasPoint, QKeyEvent *event )
651{
652 const double canvasDpi = canvas()->window()->windowHandle()->screen()->physicalDotsPerInch();
653
654 // increment used for cursor key item movement
655 double incrementPixels = 0.0;
656 if ( event->modifiers() & Qt::ShiftModifier )
657 {
658 //holding shift while pressing cursor keys results in a big step - 20 mm
659 incrementPixels = 20.0 / 25.4 * canvasDpi;
660 }
661 else if ( event->modifiers() & Qt::AltModifier )
662 {
663 //holding alt while pressing cursor keys results in a 1 pixel step
664 incrementPixels = 1;
665 }
666 else
667 {
668 // 5 mm
669 incrementPixels = 5.0 / 25.4 * canvasDpi;
670 }
671
672 double deltaXPixels = 0;
673 double deltaYPixels = 0;
674 switch ( event->key() )
675 {
676 case Qt::Key_Left:
677 deltaXPixels = -incrementPixels;
678 break;
679 case Qt::Key_Right:
680 deltaXPixels = incrementPixels;
681 break;
682 case Qt::Key_Up:
683 deltaYPixels = -incrementPixels;
684 break;
685 case Qt::Key_Down:
686 deltaYPixels = incrementPixels;
687 break;
688 default:
689 break;
690 }
691
692 const QgsPointXY beforeMoveMapPoint = canvas()->getCoordinateTransform()->toMapCoordinates( originalCanvasPoint.x(), originalCanvasPoint.y() );
693 const QgsPointXY beforeMoveLayerPoint = toLayerCoordinates( layer, beforeMoveMapPoint );
694
695 const QgsPointXY afterMoveCanvasPoint( originalCanvasPoint.x() + deltaXPixels, originalCanvasPoint.y() + deltaYPixels );
696 const QgsPointXY afterMoveMapPoint = canvas()->getCoordinateTransform()->toMapCoordinates( afterMoveCanvasPoint.x(), afterMoveCanvasPoint.y() );
697 const QgsPointXY afterMoveLayerPoint = toLayerCoordinates( layer, afterMoveMapPoint );
698
699 return QSizeF( afterMoveLayerPoint.x() - beforeMoveLayerPoint.x(), afterMoveLayerPoint.y() - beforeMoveLayerPoint.y() );
700}
701
702const QgsRenderedAnnotationItemDetails *QgsMapToolModifyAnnotation::findClosestItemToPoint( const QgsPointXY &mapPoint, const QList<const QgsRenderedAnnotationItemDetails *> &items, QgsRectangle &bounds )
703{
704 const QgsRenderedAnnotationItemDetails *closestItem = nullptr;
705 double closestItemDistance = std::numeric_limits<double>::max();
706 double closestItemArea = std::numeric_limits<double>::max();
707
708 for ( const QgsRenderedAnnotationItemDetails *item : items )
709 {
710 const QgsAnnotationItem *annotationItem = annotationItemFromId( item->layerId(), item->itemId() );
711 if ( !annotationItem )
712 continue;
713
714 const QgsRectangle itemBounds = item->boundingBox();
715 const double itemDistance = itemBounds.contains( mapPoint ) ? 0 : itemBounds.distance( mapPoint );
716 if ( !closestItem || itemDistance < closestItemDistance || ( itemDistance == closestItemDistance && itemBounds.area() < closestItemArea ) )
717 {
718 closestItem = item;
719 closestItemDistance = itemDistance;
720 closestItemArea = itemBounds.area();
721 bounds = itemBounds;
722 }
723 }
724 return closestItem;
725}
726
727QgsAnnotationLayer *QgsMapToolModifyAnnotation::annotationLayerFromId( const QString &layerId )
728{
729 QgsAnnotationLayer *layer = qobject_cast<QgsAnnotationLayer *>( QgsProject::instance()->mapLayer( layerId ) );
730 if ( !layer && layerId == QgsProject::instance()->mainAnnotationLayer()->id() )
732 return layer;
733}
734
735QgsAnnotationItem *QgsMapToolModifyAnnotation::annotationItemFromId( const QString &layerId, const QString &itemId )
736{
737 QgsAnnotationLayer *layer = annotationLayerFromId( layerId );
738 return layer ? layer->item( itemId ) : nullptr;
739}
740
741void QgsMapToolModifyAnnotation::setHoveredItemFromPoint( const QgsPointXY &mapPoint )
742{
743 QgsRectangle searchRect = QgsRectangle( mapPoint.x(), mapPoint.y(), mapPoint.x(), mapPoint.y() );
744 searchRect.grow( searchRadiusMU( canvas() ) );
745
746 const QgsRenderedItemResults *renderedItemResults = canvas()->renderedItemResults( false );
747 if ( !renderedItemResults )
748 {
749 clearHoveredItem();
750 return;
751 }
752
753 const QList<const QgsRenderedAnnotationItemDetails *> items = renderedItemResults->renderedAnnotationItemsInBounds( searchRect );
754 if ( items.empty() )
755 {
756 clearHoveredItem();
757 return;
758 }
759
760 // find closest item
761 QgsRectangle itemBounds;
762 const QgsRenderedAnnotationItemDetails *closestItem = findClosestItemToPoint( mapPoint, items, itemBounds );
763 if ( !closestItem )
764 {
765 clearHoveredItem();
766 return;
767 }
768
769 if ( closestItem->itemId() != mHoveredItemId || closestItem->layerId() != mHoveredItemLayerId )
770 {
771 setHoveredItem( closestItem, itemBounds );
772 }
773
774 // track hovered node too!... here we want to identify the closest node to the cursor position
775 QgsAnnotationItemNode hoveredNode;
776 if ( closestItem->itemId() == mSelectedItemId && closestItem->layerId() == mSelectedItemLayerId )
777 {
778 double currentNodeDistance = std::numeric_limits<double>::max();
779 mHoveredItemNodesSpatialIndex->intersects( searchRect, [&hoveredNode, &currentNodeDistance, &mapPoint, this]( int index ) -> bool {
780 if ( index >= mHoveredItemNodes.size() )
781 return false;
782
783 const QgsAnnotationItemNode &thisNode = mHoveredItemNodes.at( index );
784 const double nodeDistance = thisNode.point().sqrDist( mapPoint );
785 if ( nodeDistance < currentNodeDistance )
786 {
787 hoveredNode = thisNode;
788 currentNodeDistance = nodeDistance;
789 }
790 return true;
791 } );
792 }
793
794 if ( hoveredNode.point().isEmpty() )
795 {
796 // no hovered node
797 if ( mHoveredNodeRubberBand )
798 mHoveredNodeRubberBand->hide();
799 setCursor( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId ? Qt::OpenHandCursor : Qt::ArrowCursor );
800 }
801 else
802 {
803 if ( !mHoveredNodeRubberBand )
804 createHoveredNodeBand();
805
806 mHoveredNodeRubberBand->reset( Qgis::GeometryType::Point );
807 mHoveredNodeRubberBand->addPoint( hoveredNode.point() );
808 mHoveredNodeRubberBand->show();
809
810 setCursor( hoveredNode.cursor() );
811 }
812}
813
814void QgsMapToolModifyAnnotation::clearHoveredItem()
815{
816 if ( mHoverRubberBand )
817 mHoverRubberBand->hide();
818 if ( mHoveredNodeRubberBand )
819 mHoveredNodeRubberBand->hide();
820
821 mHoveredItemId.clear();
822 mHoveredItemLayerId.clear();
823 mHoveredItemNodeRubberBands.clear();
824 mHoveredItemNodesSpatialIndex.reset();
825
826 setCursor( Qt::ArrowCursor );
827}
828
829void QgsMapToolModifyAnnotation::clearSelectedItem()
830{
831 if ( mSelectedRubberBand )
832 mSelectedRubberBand->hide();
833
834 const bool hadSelection = !mSelectedItemId.isEmpty();
835 mSelectedItemId.clear();
836 mSelectedItemLayerId.clear();
837 if ( hadSelection )
838 emit selectionCleared();
839}
840
841void QgsMapToolModifyAnnotation::createHoverBand()
842{
843 const double scaleFactor = canvas()->fontMetrics().xHeight() * .2;
844
845 mHoverRubberBand.reset( new QgsRubberBand( mCanvas, Qgis::GeometryType::Line ) );
846 mHoverRubberBand->setWidth( scaleFactor );
847 mHoverRubberBand->setSecondaryStrokeColor( QColor( 255, 255, 255, 100 ) );
848 mHoverRubberBand->setColor( QColor( 100, 100, 100, 155 ) );
849}
850
851void QgsMapToolModifyAnnotation::createHoveredNodeBand()
852{
853 const double scaleFactor = canvas()->fontMetrics().xHeight() * .2;
854
855 mHoveredNodeRubberBand.reset( new QgsRubberBand( mCanvas, Qgis::GeometryType::Point ) );
856 mHoveredNodeRubberBand->setIcon( QgsRubberBand::ICON_FULL_BOX );
857 mHoveredNodeRubberBand->setWidth( scaleFactor );
858 mHoveredNodeRubberBand->setIconSize( scaleFactor * 5 );
859 mHoveredNodeRubberBand->setColor( QColor( 200, 0, 120, 255 ) );
860}
861
862void QgsMapToolModifyAnnotation::createSelectedItemBand()
863{
864 const double scaleFactor = canvas()->fontMetrics().xHeight() * .2;
865
866 mSelectedRubberBand.reset( new QgsRubberBand( mCanvas, Qgis::GeometryType::Line ) );
867 mSelectedRubberBand->setWidth( scaleFactor );
868 mSelectedRubberBand->setSecondaryStrokeColor( QColor( 255, 255, 255, 100 ) );
869 mSelectedRubberBand->setColor( QColor( 50, 50, 50, 200 ) );
870}
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.
QgsMapCanvas * canvas() const
returns pointer to the tool's map canvas
virtual void setCursor(const QCursor &cursor)
Sets a user defined cursor.
QPointer< QgsMapCanvas > mCanvas
The pointer to the map canvas.
Definition qgsmaptool.h:338
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 xMinimum
double yMinimum
double xMaximum
void grow(double delta)
Grows the rectangle in place by the specified amount.
double yMaximum
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