QGIS API Documentation 3.99.0-Master (09f76ad7019)
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
19#include "RTree.h"
20#include "qgsannotationitem.h"
23#include "qgsannotationlayer.h"
24#include "qgsmapcanvas.h"
25#include "qgsproject.h"
29#include "qgsrubberband.h"
30#include "qgssnapindicator.h"
31
32#include <QScreen>
33#include <QTransform>
34#include <QWindow>
35
36#include "moc_qgsmaptoolmodifyannotation.cpp"
37
39class QgsAnnotationItemNodesSpatialIndex : public RTree<int, float, 2, float>
40{
41 public:
42 void insert( int index, const QgsRectangle &bounds )
43 {
44 std::array<float, 4> scaledBounds = scaleBounds( bounds );
45 const float aMin[2] {
46 scaledBounds[0], scaledBounds[1]
47 };
48 const float aMax[2] {
49 scaledBounds[2], scaledBounds[3]
50 };
51 this->Insert(
52 aMin,
53 aMax,
54 index
55 );
56 }
57
64 void remove( int index, const QgsRectangle &bounds )
65 {
66 std::array<float, 4> scaledBounds = scaleBounds( bounds );
67 const float aMin[2] {
68 scaledBounds[0], scaledBounds[1]
69 };
70 const float aMax[2] {
71 scaledBounds[2], scaledBounds[3]
72 };
73 this->Remove(
74 aMin,
75 aMax,
76 index
77 );
78 }
79
85 bool intersects( const QgsRectangle &bounds, const std::function<bool( int index )> &callback ) const
86 {
87 std::array<float, 4> scaledBounds = scaleBounds( bounds );
88 const float aMin[2] {
89 scaledBounds[0], scaledBounds[1]
90 };
91 const float aMax[2] {
92 scaledBounds[2], scaledBounds[3]
93 };
94 this->Search(
95 aMin,
96 aMax,
97 callback
98 );
99 return true;
100 }
101
102 private:
103 std::array<float, 4> scaleBounds( const QgsRectangle &bounds ) const
104 {
105 return {
106 static_cast<float>( bounds.xMinimum() ),
107 static_cast<float>( bounds.yMinimum() ),
108 static_cast<float>( bounds.xMaximum() ),
109 static_cast<float>( bounds.yMaximum() )
110 };
111 }
112};
114
115
122
124
126{
127 mSnapIndicator->setMatch( QgsPointLocator::Match() );
128
129 clearHoveredItem();
130 clearSelectedItem();
132}
133
135{
136 mLastHoverPoint = event->originalPixelPoint();
137 event->snapPoint();
138 mSnapIndicator->setMatch( event->mapPointMatch() );
139
140 const QgsPointXY mapPoint = event->mapPoint();
141
143 QgsAnnotationLayer *layer = annotationLayerFromId( mSelectedItemLayerId );
144 context.setCurrentItemBounds( toLayerCoordinates( layer, mSelectedItemBounds ) );
145 context.setRenderContext( QgsRenderContext::fromMapSettings( canvas()->mapSettings() ) );
146
147 switch ( mCurrentAction )
148 {
149 case Action::NoAction:
150 {
151 setHoveredItemFromPoint( mapPoint );
152 break;
153 }
154
155 case Action::MoveItem:
156 {
157 if ( QgsAnnotationItem *item = annotationItemFromId( mSelectedItemLayerId, mSelectedItemId ) )
158 {
159 const QgsVector delta = toLayerCoordinates( layer, event->mapPoint() ) - mMoveStartPointLayerCrs;
160
161 QgsAnnotationItemEditOperationTranslateItem operation( mSelectedItemId, delta.x(), delta.y(), event->pixelPoint().x() - mMoveStartPointPixels.x(), event->pixelPoint().y() - mMoveStartPointPixels.y() );
162 std::unique_ptr<QgsAnnotationItemEditOperationTransientResults> operationResults( item->transientEditResultsV2( &operation, context ) );
163 if ( operationResults )
164 {
165 mTemporaryRubberBand.reset( new QgsRubberBand( mCanvas, operationResults->representativeGeometry().type() ) );
166 const double scaleFactor = canvas()->fontMetrics().xHeight() * .2;
167 mTemporaryRubberBand->setWidth( scaleFactor );
168 mTemporaryRubberBand->setToGeometry( operationResults->representativeGeometry(), layer->crs() );
169 }
170 else
171 {
172 mTemporaryRubberBand.reset();
173 }
174 }
175 break;
176 }
177
178 case Action::MoveNode:
179 {
180 if ( QgsAnnotationItem *item = annotationItemFromId( mSelectedItemLayerId, mSelectedItemId ) )
181 {
182 const QgsPointXY endPointLayer = toLayerCoordinates( layer, event->mapPoint() );
183 QgsAnnotationItemEditOperationMoveNode operation( mSelectedItemId, mTargetNode.id(), QgsPoint( mTargetNode.point() ), QgsPoint( endPointLayer ), event->pixelPoint().x() - mMoveStartPointPixels.x(), event->pixelPoint().y() - mMoveStartPointPixels.y() );
184 std::unique_ptr<QgsAnnotationItemEditOperationTransientResults> operationResults( item->transientEditResultsV2( &operation, context ) );
185 if ( operationResults )
186 {
187 mTemporaryRubberBand.reset( new QgsRubberBand( mCanvas, operationResults->representativeGeometry().type() ) );
188 const double scaleFactor = canvas()->fontMetrics().xHeight() * .2;
189 mTemporaryRubberBand->setWidth( scaleFactor );
190 mTemporaryRubberBand->setToGeometry( operationResults->representativeGeometry(), layer->crs() );
191 }
192 else
193 {
194 mTemporaryRubberBand.reset();
195 }
196 }
197 break;
198 }
199 }
200}
201
203{
204 QgsAnnotationLayer *layer = annotationLayerFromId( mSelectedItemLayerId );
205
207 context.setCurrentItemBounds( toLayerCoordinates( layer, mSelectedItemBounds ) );
208 context.setRenderContext( QgsRenderContext::fromMapSettings( canvas()->mapSettings() ) );
209
210 switch ( mCurrentAction )
211 {
212 case Action::NoAction:
213 {
214 if ( event->button() != Qt::LeftButton )
215 return;
216
217 if ( mHoveredItemId.isEmpty() || !mHoverRubberBand )
218 {
219 clearSelectedItem();
220 }
221 else if ( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId )
222 {
223 // press is on selected item => move that item
224 if ( layer )
225 {
226 const QgsPointXY mapPoint = event->mapPoint();
227 QgsRectangle searchRect = QgsRectangle( mapPoint.x(), mapPoint.y(), mapPoint.x(), mapPoint.y() );
228 searchRect.grow( searchRadiusMU( canvas() ) );
229
230 QgsAnnotationItemNode hoveredNode;
231 double currentNodeDistance = std::numeric_limits<double>::max();
232 mHoveredItemNodesSpatialIndex->intersects( searchRect, [&hoveredNode, &currentNodeDistance, &mapPoint, this]( int index ) -> bool {
233 const QgsAnnotationItemNode &thisNode = mHoveredItemNodes.at( index );
234 const double nodeDistance = thisNode.point().sqrDist( mapPoint );
235 if ( nodeDistance < currentNodeDistance )
236 {
237 hoveredNode = thisNode;
238 currentNodeDistance = nodeDistance;
239 }
240 return true;
241 } );
242
243 mMoveStartPointCanvasCrs = mapPoint;
244 mMoveStartPointPixels = event->pixelPoint();
245 mMoveStartPointLayerCrs = toLayerCoordinates( layer, mMoveStartPointCanvasCrs );
246 if ( mHoverRubberBand )
247 mHoverRubberBand->hide();
248 if ( mSelectedRubberBand )
249 mSelectedRubberBand->hide();
250
251 if ( hoveredNode.point().isEmpty() )
252 {
253 mCurrentAction = Action::MoveItem;
254 }
255 else
256 {
257 mCurrentAction = Action::MoveNode;
258 mTargetNode = hoveredNode;
259 }
260 }
261 }
262 else
263 {
264 // press is on a different item to selected item => select that item
265 mSelectedItemId = mHoveredItemId;
266 mSelectedItemLayerId = mHoveredItemLayerId;
267 mSelectedItemBounds = mHoveredItemBounds;
268
269 if ( !mSelectedRubberBand )
270 createSelectedItemBand();
271
272 mSelectedRubberBand->copyPointsFrom( mHoverRubberBand );
273 mSelectedRubberBand->show();
274
275 setCursor( Qt::OpenHandCursor );
276
277 emit itemSelected( annotationLayerFromId( mSelectedItemLayerId ), mSelectedItemId );
278 }
279 break;
280 }
281
282 case Action::MoveItem:
283 {
284 if ( event->button() == Qt::RightButton )
285 {
286 mCurrentAction = Action::NoAction;
287 mTemporaryRubberBand.reset();
288 if ( mSelectedRubberBand )
289 {
290 mSelectedRubberBand->setTranslationOffset( 0, 0 );
291 mSelectedRubberBand->show();
292 }
293 mHoveredItemNodeRubberBands.clear();
294 setCursor( Qt::ArrowCursor );
295 }
296 else if ( event->button() == Qt::LeftButton )
297 {
298 // apply move
299 if ( layer )
300 {
301 const QgsVector delta = toLayerCoordinates( layer, event->mapPoint() ) - mMoveStartPointLayerCrs;
302
303 QgsAnnotationItemEditOperationTranslateItem operation( mSelectedItemId, delta.x(), delta.y(), event->pixelPoint().x() - mMoveStartPointPixels.x(), event->pixelPoint().y() - mMoveStartPointPixels.y() );
304 switch ( layer->applyEditV2( &operation, context ) )
305 {
308 mRefreshSelectedItemAfterRedraw = true;
309 break;
312 break;
313 }
314 }
315
316 mTemporaryRubberBand.reset();
317 mCurrentAction = Action::NoAction;
318 setCursor( Qt::ArrowCursor );
319 }
320 break;
321 }
322
323 case Action::MoveNode:
324 {
325 if ( event->button() == Qt::RightButton )
326 {
327 mCurrentAction = Action::NoAction;
328 mTemporaryRubberBand.reset();
329 mHoveredItemNodeRubberBands.clear();
330 mTemporaryRubberBand.reset();
331 setCursor( Qt::ArrowCursor );
332 }
333 else if ( event->button() == Qt::LeftButton )
334 {
335 if ( layer )
336 {
337 const QgsPointXY endPointLayer = toLayerCoordinates( layer, event->mapPoint() );
338 QgsAnnotationItemEditOperationMoveNode operation( mSelectedItemId, mTargetNode.id(), QgsPoint( mTargetNode.point() ), QgsPoint( endPointLayer ), event->pixelPoint().x() - mMoveStartPointPixels.x(), 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 if ( !layer || mSelectedItemId.isEmpty() )
438 return;
439
440 layer->removeItem( mSelectedItemId );
441 clearSelectedItem();
442 clearHoveredItem();
443 event->ignore(); // disable default shortcut handling
444 }
445 else if ( event->key() == Qt::Key_Left
446 || event->key() == Qt::Key_Right
447 || event->key() == Qt::Key_Up
448 || event->key() == Qt::Key_Down )
449 {
450 if ( !layer )
451 return;
452
453 const QSizeF deltaLayerCoordinates = deltaForKeyEvent( layer, mSelectedRubberBand->asGeometry().centroid().asPoint(), event );
454
455 QgsAnnotationItemEditOperationTranslateItem operation( mSelectedItemId, deltaLayerCoordinates.width(), deltaLayerCoordinates.height() );
456 switch ( layer->applyEditV2( &operation, context ) )
457 {
460 mRefreshSelectedItemAfterRedraw = true;
461 break;
464 break;
465 }
466 event->ignore(); // disable default shortcut handling (move map)
467 }
468 break;
469 }
470
471 case Action::MoveNode:
472 {
473 if ( event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace )
474 {
475 if ( layer )
476 {
477 QgsAnnotationItemEditOperationDeleteNode operation( mSelectedItemId, mTargetNode.id(), QgsPoint( mTargetNode.point() ) );
478 switch ( layer->applyEditV2( &operation, context ) )
479 {
482 mRefreshSelectedItemAfterRedraw = true;
483 break;
485 break;
488 break;
489 }
490 }
491
492 mTemporaryRubberBand.reset();
493 mHoveredItemNodeRubberBands.clear();
494 mHoveredItemNodes.clear();
495 mTemporaryRubberBand.reset();
496 mCurrentAction = Action::NoAction;
497 setCursor( Qt::ArrowCursor );
498 event->ignore(); // disable default shortcut handling (delete vector feature)
499 break;
500 }
501 [[fallthrough]];
502 }
503
504 case Action::MoveItem:
505 {
506 // warning -- fallthrough above!
507 if ( event->key() == Qt::Key_Escape )
508 {
509 mCurrentAction = Action::NoAction;
510 mTemporaryRubberBand.reset();
511 if ( mSelectedRubberBand )
512 {
513 mSelectedRubberBand->setTranslationOffset( 0, 0 );
514 mSelectedRubberBand->show();
515 }
516 mHoveredItemNodeRubberBands.clear();
517
518 setCursor( Qt::ArrowCursor );
519 }
520 break;
521 }
522 }
523}
524
525void QgsMapToolModifyAnnotation::onCanvasRefreshed()
526{
527 bool needsSelectedItemRefresh = mRefreshSelectedItemAfterRedraw;
528 if ( QgsAnnotationItem *item = annotationItemFromId( mSelectedItemLayerId, mSelectedItemId ) )
529 {
531 {
532 needsSelectedItemRefresh = true;
533 }
534 }
535
536 if ( needsSelectedItemRefresh )
537 {
538 const QgsRenderedItemResults *renderedItemResults = canvas()->renderedItemResults( false );
539 if ( !renderedItemResults )
540 {
541 return;
542 }
543
544 const QList<QgsRenderedItemDetails *> items = renderedItemResults->renderedItems();
545 auto it = std::find_if( items.begin(), items.end(), [this]( const QgsRenderedItemDetails *item ) {
546 if ( const QgsRenderedAnnotationItemDetails *annotationItem = dynamic_cast<const QgsRenderedAnnotationItemDetails *>( item ) )
547 {
548 if ( annotationItem->itemId() == mSelectedItemId && annotationItem->layerId() == mSelectedItemLayerId )
549 return true;
550 }
551 return false;
552 } );
553 if ( it != items.end() )
554 {
555 const QgsRectangle itemBounds = ( *it )->boundingBox();
556
557 setHoveredItem( dynamic_cast<const QgsRenderedAnnotationItemDetails *>( *it ), itemBounds );
558 if ( !mSelectedRubberBand )
559 createSelectedItemBand();
560
561 mSelectedRubberBand->copyPointsFrom( mHoverRubberBand );
562 mSelectedRubberBand->show();
563 mSelectedItemBounds = mHoveredItemBounds;
564 }
565 }
566 else
567 {
568 // recheck for hovered item at new mouse point
569 const QgsPointXY mapPoint = canvas()->mapSettings().mapToPixel().toMapCoordinates( mLastHoverPoint );
570 setHoveredItemFromPoint( mapPoint );
571 }
572 mRefreshSelectedItemAfterRedraw = false;
573}
574
575void QgsMapToolModifyAnnotation::setHoveredItem( const QgsRenderedAnnotationItemDetails *item, const QgsRectangle &itemMapBounds )
576{
577 mHoveredItemNodeRubberBands.clear();
578 if ( mHoveredNodeRubberBand )
579 mHoveredNodeRubberBand->hide();
580 mHoveredItemId = item->itemId();
581 mHoveredItemLayerId = item->layerId();
582 mHoveredItemBounds = itemMapBounds;
583 if ( !mHoverRubberBand )
584 createHoverBand();
585
586 mHoverRubberBand->show();
587
588 mHoverRubberBand->reset( Qgis::GeometryType::Line );
589 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.xMinimum(), itemMapBounds.yMinimum() ) );
590 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.xMaximum(), itemMapBounds.yMinimum() ) );
591 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.xMaximum(), itemMapBounds.yMaximum() ) );
592 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.xMinimum(), itemMapBounds.yMaximum() ) );
593 mHoverRubberBand->addPoint( QgsPointXY( itemMapBounds.xMinimum(), itemMapBounds.yMinimum() ) );
594
595 QgsAnnotationLayer *layer = annotationLayerFromId( item->layerId() );
596 const QgsAnnotationItem *annotationItem = annotationItemFromId( item->layerId(), item->itemId() );
597 if ( !annotationItem )
598 return;
599
600 QgsCoordinateTransform layerToMapTransform = QgsCoordinateTransform( layer->crs(), canvas()->mapSettings().destinationCrs(), canvas()->mapSettings().transformContext() );
601
602 const double scaleFactor = canvas()->fontMetrics().xHeight() * .2;
603
604 QgsAnnotationItemEditContext context;
605 context.setCurrentItemBounds( toLayerCoordinates( layer, itemMapBounds ) );
606 context.setRenderContext( QgsRenderContext::fromMapSettings( canvas()->mapSettings() ) );
607
608 const QList<QgsAnnotationItemNode> itemNodes = annotationItem->nodesV2( context );
609 QgsRubberBand *vertexNodeBand = new QgsRubberBand( mCanvas, Qgis::GeometryType::Point );
610
611 vertexNodeBand->setIcon( QgsRubberBand::ICON_BOX );
612 vertexNodeBand->setWidth( scaleFactor );
613 vertexNodeBand->setIconSize( scaleFactor * 5 );
614 vertexNodeBand->setColor( QColor( 200, 0, 120, 255 ) );
615
616 QgsRubberBand *calloutNodeBand = new QgsRubberBand( mCanvas, Qgis::GeometryType::Point );
617 calloutNodeBand->setWidth( scaleFactor );
618 calloutNodeBand->setSecondaryStrokeColor( QColor( 255, 255, 255, 100 ) );
619 calloutNodeBand->setColor( QColor( 120, 200, 0, 255 ) );
620 calloutNodeBand->setIcon( QgsRubberBand::ICON_X );
621 calloutNodeBand->setIconSize( scaleFactor * 5 );
622
623 // store item nodes in a spatial index for quick searching
624 mHoveredItemNodesSpatialIndex = std::make_unique<QgsAnnotationItemNodesSpatialIndex>();
625 int index = 0;
626 mHoveredItemNodes.clear();
627 mHoveredItemNodes.reserve( itemNodes.size() );
628 for ( const QgsAnnotationItemNode &node : itemNodes )
629 {
630 QgsPointXY nodeMapPoint;
631 try
632 {
633 nodeMapPoint = layerToMapTransform.transform( node.point() );
634 }
635 catch ( QgsCsException & )
636 {
637 continue;
638 }
639
640 switch ( node.type() )
641 {
643 vertexNodeBand->addPoint( nodeMapPoint );
644 break;
645
647 calloutNodeBand->addPoint( nodeMapPoint );
648 break;
649 }
650
651 mHoveredItemNodesSpatialIndex->insert( index, QgsRectangle( nodeMapPoint.x(), nodeMapPoint.y(), nodeMapPoint.x(), nodeMapPoint.y() ) );
652
653 QgsAnnotationItemNode transformedNode = node;
654 transformedNode.setPoint( nodeMapPoint );
655 mHoveredItemNodes.append( transformedNode );
656
657 index++;
658 }
659
660 mHoveredItemNodeRubberBands.emplace_back( vertexNodeBand );
661 mHoveredItemNodeRubberBands.emplace_back( calloutNodeBand );
662}
663
664QSizeF QgsMapToolModifyAnnotation::deltaForKeyEvent( QgsAnnotationLayer *layer, const QgsPointXY &originalCanvasPoint, QKeyEvent *event )
665{
666 const double canvasDpi = canvas()->window()->windowHandle()->screen()->physicalDotsPerInch();
667
668 // increment used for cursor key item movement
669 double incrementPixels = 0.0;
670 if ( event->modifiers() & Qt::ShiftModifier )
671 {
672 //holding shift while pressing cursor keys results in a big step - 20 mm
673 incrementPixels = 20.0 / 25.4 * canvasDpi;
674 }
675 else if ( event->modifiers() & Qt::AltModifier )
676 {
677 //holding alt while pressing cursor keys results in a 1 pixel step
678 incrementPixels = 1;
679 }
680 else
681 {
682 // 5 mm
683 incrementPixels = 5.0 / 25.4 * canvasDpi;
684 }
685
686 double deltaXPixels = 0;
687 double deltaYPixels = 0;
688 switch ( event->key() )
689 {
690 case Qt::Key_Left:
691 deltaXPixels = -incrementPixels;
692 break;
693 case Qt::Key_Right:
694 deltaXPixels = incrementPixels;
695 break;
696 case Qt::Key_Up:
697 deltaYPixels = -incrementPixels;
698 break;
699 case Qt::Key_Down:
700 deltaYPixels = incrementPixels;
701 break;
702 default:
703 break;
704 }
705
706 const QgsPointXY beforeMoveMapPoint = canvas()->getCoordinateTransform()->toMapCoordinates( originalCanvasPoint.x(), originalCanvasPoint.y() );
707 const QgsPointXY beforeMoveLayerPoint = toLayerCoordinates( layer, beforeMoveMapPoint );
708
709 const QgsPointXY afterMoveCanvasPoint( originalCanvasPoint.x() + deltaXPixels, originalCanvasPoint.y() + deltaYPixels );
710 const QgsPointXY afterMoveMapPoint = canvas()->getCoordinateTransform()->toMapCoordinates( afterMoveCanvasPoint.x(), afterMoveCanvasPoint.y() );
711 const QgsPointXY afterMoveLayerPoint = toLayerCoordinates( layer, afterMoveMapPoint );
712
713 return QSizeF( afterMoveLayerPoint.x() - beforeMoveLayerPoint.x(), afterMoveLayerPoint.y() - beforeMoveLayerPoint.y() );
714}
715
716void QgsMapToolModifyAnnotation::setHoveredItemFromPoint( const QgsPointXY &mapPoint )
717{
718 QgsRectangle searchRect = QgsRectangle( mapPoint.x(), mapPoint.y(), mapPoint.x(), mapPoint.y() );
719 searchRect.grow( searchRadiusMU( canvas() ) );
720
721 const QgsRenderedItemResults *renderedItemResults = canvas()->renderedItemResults( false );
722 if ( !renderedItemResults )
723 {
724 clearHoveredItem();
725 return;
726 }
727
728 const QList<const QgsRenderedAnnotationItemDetails *> items = renderedItemResults->renderedAnnotationItemsInBounds( searchRect );
729 if ( items.empty() )
730 {
731 clearHoveredItem();
732 return;
733 }
734
735 // find closest item
736 QgsRectangle itemBounds;
737 const QgsRenderedAnnotationItemDetails *closestItem = findClosestItemToPoint( mapPoint, items, itemBounds );
738 if ( !closestItem )
739 {
740 clearHoveredItem();
741 return;
742 }
743
744 if ( closestItem->itemId() != mHoveredItemId || closestItem->layerId() != mHoveredItemLayerId )
745 {
746 setHoveredItem( closestItem, itemBounds );
747 }
748
749 // track hovered node too!... here we want to identify the closest node to the cursor position
750 QgsAnnotationItemNode hoveredNode;
751 if ( closestItem->itemId() == mSelectedItemId && closestItem->layerId() == mSelectedItemLayerId )
752 {
753 double currentNodeDistance = std::numeric_limits<double>::max();
754 mHoveredItemNodesSpatialIndex->intersects( searchRect, [&hoveredNode, &currentNodeDistance, &mapPoint, this]( int index ) -> bool {
755 if ( index >= mHoveredItemNodes.size() )
756 return false;
757
758 const QgsAnnotationItemNode &thisNode = mHoveredItemNodes.at( index );
759 const double nodeDistance = thisNode.point().sqrDist( mapPoint );
760 if ( nodeDistance < currentNodeDistance )
761 {
762 hoveredNode = thisNode;
763 currentNodeDistance = nodeDistance;
764 }
765 return true;
766 } );
767 }
768
769 if ( hoveredNode.point().isEmpty() )
770 {
771 // no hovered node
772 if ( mHoveredNodeRubberBand )
773 mHoveredNodeRubberBand->hide();
774 setCursor( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId ? Qt::OpenHandCursor : Qt::ArrowCursor );
775 }
776 else
777 {
778 if ( !mHoveredNodeRubberBand )
779 createHoveredNodeBand();
780
781 mHoveredNodeRubberBand->reset( Qgis::GeometryType::Point );
782 mHoveredNodeRubberBand->addPoint( hoveredNode.point() );
783 mHoveredNodeRubberBand->show();
784
785 setCursor( hoveredNode.cursor() );
786 }
787}
788
789void QgsMapToolModifyAnnotation::clearHoveredItem()
790{
791 if ( mHoverRubberBand )
792 mHoverRubberBand->hide();
793 if ( mHoveredNodeRubberBand )
794 mHoveredNodeRubberBand->hide();
795
796 mHoveredItemId.clear();
797 mHoveredItemLayerId.clear();
798 mHoveredItemNodeRubberBands.clear();
799 mHoveredItemNodesSpatialIndex.reset();
800
801 setCursor( Qt::ArrowCursor );
802}
803
804void QgsMapToolModifyAnnotation::clearSelectedItem()
805{
806 if ( mSelectedRubberBand )
807 mSelectedRubberBand->hide();
808
809 const bool hadSelection = !mSelectedItemId.isEmpty();
810 mSelectedItemId.clear();
811 mSelectedItemLayerId.clear();
812 if ( hadSelection )
813 emit selectionCleared();
814}
815
816void QgsMapToolModifyAnnotation::createHoverBand()
817{
818 const double scaleFactor = canvas()->fontMetrics().xHeight() * .2;
819
820 mHoverRubberBand.reset( new QgsRubberBand( mCanvas, Qgis::GeometryType::Line ) );
821 mHoverRubberBand->setWidth( scaleFactor );
822 mHoverRubberBand->setSecondaryStrokeColor( QColor( 255, 255, 255, 100 ) );
823 mHoverRubberBand->setColor( QColor( 100, 100, 100, 155 ) );
824}
825
826void QgsMapToolModifyAnnotation::createHoveredNodeBand()
827{
828 const double scaleFactor = canvas()->fontMetrics().xHeight() * .2;
829
830 mHoveredNodeRubberBand.reset( new QgsRubberBand( mCanvas, Qgis::GeometryType::Point ) );
831 mHoveredNodeRubberBand->setIcon( QgsRubberBand::ICON_FULL_BOX );
832 mHoveredNodeRubberBand->setWidth( scaleFactor );
833 mHoveredNodeRubberBand->setIconSize( scaleFactor * 5 );
834 mHoveredNodeRubberBand->setColor( QColor( 200, 0, 120, 255 ) );
835}
836
837void QgsMapToolModifyAnnotation::createSelectedItemBand()
838{
839 const double scaleFactor = canvas()->fontMetrics().xHeight() * .2;
840
841 mSelectedRubberBand.reset( new QgsRubberBand( mCanvas, Qgis::GeometryType::Line ) );
842 mSelectedRubberBand->setWidth( scaleFactor );
843 mSelectedRubberBand->setSecondaryStrokeColor( QColor( 255, 255, 255, 100 ) );
844 mSelectedRubberBand->setColor( QColor( 50, 50, 50, 200 ) );
845}
@ VertexHandle
Node is a handle for manipulating vertices.
Definition qgis.h:2566
@ CalloutHandle
Node is a handle for manipulating callouts.
Definition qgis.h:2567
@ ScaleDependentBoundingBox
Item's bounding box will vary depending on map scale.
Definition qgis.h:2523
@ Invalid
Operation has invalid parameters for the item, no change occurred.
Definition qgis.h:2579
@ Success
Item was modified successfully.
Definition qgis.h:2578
@ ItemCleared
The operation results in the item being cleared, and the item should be removed from the layer as a r...
Definition qgis.h:2580
@ Point
Points.
Definition qgis.h:366
@ Line
Lines.
Definition qgis.h:367
A dockable widget used to handle the CAD tools on top of a selection of map tools.
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.
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.
QgsAnnotationLayer * annotationLayerFromId(const QString &layerId)
Returns the annotation layer matching a given ID.
QgsAnnotationMapTool(QgsMapCanvas *canvas, QgsAdvancedDigitizingDockWidget *cadDockWidget)
Constructor for QgsAnnotationMapTool.
const QgsRenderedAnnotationItemDetails * findClosestItemToPoint(const QgsPointXY &mapPoint, const QList< const QgsRenderedAnnotationItemDetails * > &items, QgsRectangle &bounds)
Returns the closest item from a list of annotation items to a given map point.
QgsAnnotationItem * annotationItemFromId(const QString &layerId, const QString &itemId)
Returns the annotation item matching a given pair of layer and item IDs.
QgsPointXY transform(const QgsPointXY &point, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transform the point from the source CRS to the destination CRS.
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.
A mouse event which is the result of a user interaction with 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.
void deactivate() override
Unregisters this maptool from the cad dock widget.
virtual QgsMapLayer * layer() const
Returns the layer associated with the map tool.
QgsAdvancedDigitizingDockWidget * cadDockWidget() const
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.
QgsMapLayer * layer(const QString &id)
Returns the map layer with the matching ID, or nullptr if no layers could be found.
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:369
static double searchRadiusMU(const QgsRenderContext &context)
Gets search radius in map units for given context.
Represents a 2D point.
Definition qgspointxy.h:62
double sqrDist(double x, double y) const
Returns the squared distance between this point a specified x, y coordinate.
Definition qgspointxy.h:188
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
bool isEmpty() const
Returns true if the geometry is empty.
Definition qgspointxy.h:244
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
static QgsProject * instance()
Returns the QgsProject singleton instance.
void setDirty(bool b=true)
Flag the project as dirty (modified).
A rectangle specified with double values.
double xMinimum
double yMinimum
double xMaximum
void grow(double delta)
Grows the rectangle in place by the specified amount.
double yMaximum
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.
QString layerId() const
Returns the layer ID of the associated map layer.
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.
Responsible for drawing transient features (e.g.
void setIconSize(double iconSize)
Sets the size of the point icons.
void setWidth(double width)
Sets the width of the line.
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 setIcon(IconType icon)
Sets the icon type to highlight point geometries.
void addPoint(const QgsPointXY &p, bool doUpdate=true, int geometryIndex=0, int ringIndex=0)
Adds a vertex to the rubberband and update canvas.
Shows a snapping marker on map canvas for the current snapping match.
Represent a 2-dimensional vector.
Definition qgsvector.h:34
double y() const
Returns the vector's y-component.
Definition qgsvector.h:156
double x() const
Returns the vector's x-component.
Definition qgsvector.h:147