QGIS API Documentation 4.3.0-Master (0de80482b60)
Loading...
Searching...
No Matches
qgsmaptoolselectannotation.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsmaptoolselectannotation.cpp
3 ----------------
4 copyright : (C) 2025 by Mathieu Pellerin
5 email : mathieu at opengis dot ch
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 <algorithm>
20#include <limits>
21
22#include "qgsannotationitem.h"
25#include "qgsannotationlayer.h"
27#include "qgsapplication.h"
29#include "qgsgeometry.h"
30#include "qgslogger.h"
31#include "qgsmapcanvas.h"
33#include "qgsmaptopixel.h"
34#include "qgsproject.h"
35#include "qgsrendercontext.h"
39#include "qgsrubberband.h"
40#include "qgssnapindicator.h"
41
42#include <QGraphicsSceneHoverEvent>
43#include <QKeySequence>
44#include <QScreen>
45#include <QString>
46#include <QTransform>
47#include <QWindow>
48
49#include "moc_qgsmaptoolselectannotation.cpp"
50
51using namespace Qt::StringLiterals;
52
54 : QgsRubberBand( canvas, Qgis::GeometryType::Line )
55 , mLayerId( layerId )
56 , mItemId( itemId )
57{
58 setFlags( flags() | QGraphicsItem::ItemIsSelectable );
59
60 setWidth( canvas->fontMetrics().xHeight() * .2 );
61 setSecondaryStrokeColor( QColor( 255, 255, 255, 100 ) );
62 setColor( QColor( 50, 50, 50, 200 ) );
63 setZValue( 10 );
64 setSelected( false );
65}
66
68{
69 QgsAnnotationLayer *lyr = qobject_cast<QgsAnnotationLayer *>( QgsProject::instance()->mapLayer( mLayerId ) );
70 if ( !lyr && mLayerId == QgsProject::instance()->mainAnnotationLayer()->id() )
71 {
73 }
74 return lyr;
75}
76
78{
80 return lyr ? lyr->item( mItemId ) : nullptr;
81}
82
84{
85 mBoundingBox = boundingBox;
86 QgsAnnotationItem *annotationItem = item();
87 mNeedsUpdatedBoundingBox = annotationItem && ( annotationItem->flags() & Qgis::AnnotationItemFlag::ScaleDependentBoundingBox );
88
90
91 const QgsMapToPixel *transform = mMapCanvas->getCoordinateTransform();
92
93 const QgsAnnotationRectItem *rectItem = dynamic_cast< const QgsAnnotationRectItem * >( annotationItem );
94 const double mapRotation = mMapCanvas->mapSettings().rotation();
95 const double frameRotation = rectItem ? rectItem->appliedRotation( mapRotation ) - mapRotation : 0;
96
97 if ( rectItem && !qgsDoubleNear( frameRotation, 0 ) )
98 {
99 double minX = std::numeric_limits<double>::max();
100 double minY = std::numeric_limits<double>::max();
101 double maxX = std::numeric_limits<double>::lowest();
102 double maxY = std::numeric_limits<double>::lowest();
103 auto includePixel = [&]( const QPointF &p ) {
104 minX = std::min( minX, p.x() );
105 minY = std::min( minY, p.y() );
106 maxX = std::max( maxX, p.x() );
107 maxY = std::max( maxY, p.y() );
108 };
109
110 // rotated item corners, as drawn on screen
111 if ( QgsAnnotationLayer *lyr = layer() )
112 {
113 const QgsCoordinateTransform layerToMap( lyr->crs(), mMapCanvas->mapSettings().destinationCrs(), mMapCanvas->mapSettings().transformContext() );
114 const QgsRenderContext renderContext = QgsRenderContext::fromMapSettings( mMapCanvas->mapSettings() );
115 const QPolygonF corners = rectItem->rotatedBoundsGeometry( rectItem->bounds(), renderContext ).asQPolygonF();
116 for ( const QPointF &corner : corners )
117 {
118 QgsPointXY mapPoint;
119 try
120 {
121 mapPoint = layerToMap.transform( QgsPointXY( corner.x(), corner.y() ) );
122 }
123 catch ( QgsCsException & )
124 {
125 // the band is still bounded by the item bounding box corners below
126 QgsDebugError( u"Error transforming annotation item corner"_s );
127 continue;
128 }
129 includePixel( toCanvasCoordinates( mapPoint ) );
130 }
131 }
132
133 // callout anchor and everything else reported by the incoming bounding box
134 const QgsPointXY bboxCorners[4] = {
135 QgsPointXY( boundingBox.xMinimum(), boundingBox.yMinimum() ),
136 QgsPointXY( boundingBox.xMaximum(), boundingBox.yMinimum() ),
137 QgsPointXY( boundingBox.xMaximum(), boundingBox.yMaximum() ),
138 QgsPointXY( boundingBox.xMinimum(), boundingBox.yMaximum() ),
139 };
140 for ( const QgsPointXY &corner : bboxCorners )
141 includePixel( toCanvasCoordinates( corner ) );
142
143 const QgsPointXY topLeft = transform->toMapCoordinates( minX, minY );
144 const QgsPointXY topRight = transform->toMapCoordinates( maxX, minY );
145 const QgsPointXY bottomRight = transform->toMapCoordinates( maxX, maxY );
146 const QgsPointXY bottomLeft = transform->toMapCoordinates( minX, maxY );
147 addPoint( topLeft );
148 addPoint( topRight );
149 addPoint( bottomRight );
150 addPoint( bottomLeft );
151 addPoint( topLeft );
152 }
153 else
154 {
155 // Build the rubber band as a screen-aligned rectangle matching the item's
156 // on-screen size.
157 const QPointF centerCanvas = toCanvasCoordinates( boundingBox.center() );
158 const double mupp = mMapCanvas->mapSettings().mapUnitsPerPixel();
159 const double halfWidth = 0.5 * boundingBox.width() / mupp;
160 const double halfHeight = 0.5 * boundingBox.height() / mupp;
161 const QgsPointXY topLeft = transform->toMapCoordinates( centerCanvas.x() - halfWidth, centerCanvas.y() - halfHeight );
162 const QgsPointXY topRight = transform->toMapCoordinates( centerCanvas.x() + halfWidth, centerCanvas.y() - halfHeight );
163 const QgsPointXY bottomRight = transform->toMapCoordinates( centerCanvas.x() + halfWidth, centerCanvas.y() + halfHeight );
164 const QgsPointXY bottomLeft = transform->toMapCoordinates( centerCanvas.x() - halfWidth, centerCanvas.y() + halfHeight );
165 addPoint( topLeft );
166 addPoint( topRight );
167 addPoint( bottomRight );
168 addPoint( bottomLeft );
169 addPoint( topLeft );
170 }
171
172 show();
173 setSelected( true );
174}
175
180
181
184{
185 connect( QgsMapToolSelectAnnotation::canvas(), &QgsMapCanvas::mapCanvasRefreshed, this, &QgsMapToolSelectAnnotation::onCanvasRefreshed );
186
188 for ( std::unique_ptr<QgsAnnotationItemRubberBand> &selectedItem : mSelectedItems )
189 selectedItem->updateBoundingBox( selectedItem->boundingBox() );
190 } );
191
193}
194
196{
197 mSelectionRubberBand.reset();
198 mMouseHandles.reset();
199}
200
207
209{
210 mSelectionRubberBand.reset();
211 mMouseHandles.reset();
212
213 mSelectedItems.clear();
214
215 mCopiedItems.clear();
216 mCopiedItemsTopLeft = QPointF();
217
219}
220
222{
223 const QPointF scenePos = mCanvas->mapToScene( event->pos() );
224
225 if ( event->buttons() == Qt::NoButton )
226 {
227 if ( mMouseHandles->isDragging() )
228 {
229 QGraphicsSceneMouseEvent forwardedEvent( QEvent::GraphicsSceneMouseMove );
230 forwardedEvent.setPos( mMouseHandles->mapFromScene( scenePos ) );
231 forwardedEvent.setScenePos( scenePos );
232 forwardedEvent.setLastScenePos( mLastScenePos );
233 forwardedEvent.setButton( Qt::LeftButton );
234 mMouseHandles->mouseMoveEvent( &forwardedEvent );
235 }
236 else
237 {
238 if ( mMouseHandles->sceneBoundingRect().contains( scenePos ) )
239 {
240 QGraphicsSceneHoverEvent forwardedEvent( QEvent::GraphicsSceneHoverMove );
241 forwardedEvent.setPos( mMouseHandles->mapFromScene( scenePos ) );
242 forwardedEvent.setScenePos( scenePos );
243 mMouseHandles->hoverMoveEvent( &forwardedEvent );
244 mHoveringMouseHandles = true;
245 }
246 else if ( mHoveringMouseHandles )
247 {
248 QGraphicsSceneHoverEvent forwardedEvent( QEvent::GraphicsSceneHoverLeave );
249 forwardedEvent.setPos( mMouseHandles->mapFromScene( scenePos ) );
250 forwardedEvent.setScenePos( scenePos );
251 mMouseHandles->hoverMoveEvent( &forwardedEvent );
252 mHoveringMouseHandles = false;
253 }
254 }
255 }
256 else if ( event->buttons() == Qt::LeftButton )
257 {
258 if ( mMouseHandles->shouldBlockEvent( event ) )
259 {
260 QGraphicsSceneMouseEvent forwardedEvent( QEvent::GraphicsSceneMouseMove );
261 forwardedEvent.setPos( mMouseHandles->mapFromScene( scenePos ) );
262 forwardedEvent.setScenePos( scenePos );
263 forwardedEvent.setLastScenePos( mLastScenePos );
264 forwardedEvent.setButton( Qt::LeftButton );
265 mMouseHandles->mouseMoveEvent( &forwardedEvent );
266 }
267 else
268 {
269 if ( !mDragging )
270 {
271 mDragging = true;
273 QColor color( Qt::blue );
274 color.setAlpha( 63 );
275 mSelectionRubberBand->setColor( color );
276 mSelectionRect.setTopLeft( event->pos() );
277 }
278
279 mSelectionRect.setBottomRight( event->pos() );
280 if ( mSelectionRubberBand )
281 {
282 mSelectionRubberBand->setToCanvasRectangle( mSelectionRect );
283 mSelectionRubberBand->show();
284 }
285 }
286 }
287
288 mLastScenePos = scenePos;
289}
290
292{
293 if ( event->button() != Qt::LeftButton )
294 {
295 return;
296 }
297
298 QPointF scenePos = mCanvas->mapToScene( event->pos() );
299 const bool toggleSelection = event->modifiers() & Qt::ShiftModifier;
300
301 if ( !toggleSelection && !mSelectedItems.empty() && mMouseHandles->sceneBoundingRect().contains( scenePos ) )
302 {
303 QGraphicsSceneMouseEvent forwardedEvent( QEvent::GraphicsSceneMousePress );
304 forwardedEvent.setPos( mMouseHandles->mapFromScene( scenePos ) );
305 forwardedEvent.setScenePos( scenePos );
306 forwardedEvent.setLastScenePos( mLastScenePos );
307 forwardedEvent.setButton( Qt::LeftButton );
308 mMouseHandles->mousePressEvent( &forwardedEvent );
309 }
310 else
311 {
312 mSelectionRect.setTopLeft( event->pos() );
313 mSelectionRect.setBottomRight( event->pos() );
314 }
315
316 mLastScenePos = scenePos;
317}
318
320{
321 if ( event->button() != Qt::LeftButton )
322 {
323 return;
324 }
325
326 QPointF scenePos = mCanvas->mapToScene( event->pos() );
327
328 if ( mMouseHandles->shouldBlockEvent( event ) )
329 {
330 QGraphicsSceneMouseEvent forwardedEvent( QEvent::GraphicsSceneMouseRelease );
331 forwardedEvent.setPos( mMouseHandles->mapFromScene( scenePos ) );
332 forwardedEvent.setScenePos( scenePos );
333 forwardedEvent.setLastScenePos( mLastScenePos );
334 forwardedEvent.setButton( Qt::LeftButton );
335 mMouseHandles->mouseReleaseEvent( &forwardedEvent );
336 }
337 else
338 {
339 if ( mCanceled )
340 {
341 mCanceled = false;
342 mDragging = false;
343 return;
344 }
345
346 if ( mDragging )
347 {
348 mDragging = false;
349 mSelectionRubberBand.reset();
350
351 const QgsPointXY topLeft = toMapCoordinates( mSelectionRect.topLeft() );
352 const QgsPointXY bottomRight = toMapCoordinates( mSelectionRect.bottomRight() );
353 const QgsRectangle searchRect( topLeft, bottomRight );
354
355 setSelectedItemsFromRect( searchRect, ( event->modifiers() & Qt::ShiftModifier ) );
356 }
357 else
358 {
359 setSelectedItemFromPoint( event->mapPoint(), ( event->modifiers() & Qt::ShiftModifier ) );
360 }
361
362 mMouseHandles->setSelected( !mSelectedItems.empty() );
363 }
364}
365
367{
368 if ( mMouseHandles->isDragging() || mMouseHandles->isResizing() || mMouseHandles->isRotating() )
369 {
370 return;
371 }
372
373 if ( mSelectedItems.empty() )
374 {
375 return;
376 }
377
378 if ( event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete )
379 {
380 while ( !mSelectedItems.empty() )
381 {
382 if ( QgsAnnotationLayer *annotationLayer = mSelectedItems.back()->layer() )
383 {
384 annotationLayer->removeItem( mSelectedItems.back()->itemId() );
385 }
386 mSelectedItems.pop_back();
387 }
389 updateSelectedItem();
390 event->ignore();
391 }
392 else if ( event->key() == Qt::Key_Left || event->key() == Qt::Key_Right || event->key() == Qt::Key_Up || event->key() == Qt::Key_Down )
393 {
394 const int pixels = ( event->modifiers() & Qt::ShiftModifier ) ? 1 : 50;
395 int deltaX = 0;
396 int deltaY = 0;
397 if ( event->key() == Qt::Key_Up )
398 {
399 deltaY = -pixels;
400 }
401 else if ( event->key() == Qt::Key_Down )
402 {
403 deltaY = pixels;
404 }
405 else if ( event->key() == Qt::Key_Left )
406 {
407 deltaX = -pixels;
408 }
409 else if ( event->key() == Qt::Key_Right )
410 {
411 deltaX = pixels;
412 }
413
414 for ( std::unique_ptr<QgsAnnotationItemRubberBand> &selectedItem : mSelectedItems )
415 {
416 attemptMoveBy( selectedItem.get(), deltaX, deltaY );
417 }
418 event->ignore();
419 }
420}
421
423{
424 if ( mMouseHandles->isDragging() || mMouseHandles->isResizing() || mMouseHandles->isRotating() )
425 {
426 return true;
427 }
428 // NOLINTBEGIN(bugprone-narrowing-conversions)
429 QKeySequence keySequence( event->key() | event->modifiers() );
430 // NOLINTEND(bugprone-narrowing-conversions)
431 if ( keySequence == QKeySequence::Copy || keySequence == QKeySequence::Cut )
432 {
433 mCopiedItems.clear();
434 mCopiedItemsTopLeft = toMapCoordinates( QPoint( mMouseHandles->sceneBoundingRect().topLeft().x(), mMouseHandles->sceneBoundingRect().topLeft().y() ) );
435 for ( std::unique_ptr<QgsAnnotationItemRubberBand> &selectedItem : mSelectedItems )
436 {
437 if ( QgsAnnotationItem *annotationItem = selectedItem->item() )
438 {
439 std::unique_ptr<QgsAnnotationItem> copiedItem;
440 copiedItem.reset( annotationItem->clone() );
441 mCopiedItems.push_back( std::move( copiedItem ) );
442 }
443 }
444
445 if ( keySequence == QKeySequence::Cut )
446 {
447 while ( !mSelectedItems.empty() )
448 {
449 if ( QgsAnnotationLayer *annotationLayer = mSelectedItems.back()->layer() )
450 {
451 annotationLayer->removeItem( mSelectedItems.back()->itemId() );
452 }
453 mSelectedItems.pop_back();
454 }
456 }
457
458 return true;
459 }
460 else if ( keySequence == QKeySequence::Paste )
461 {
462 const QgsPointXY copiedItemsSceneTopLeft = mCanvas->mapSettings().mapToPixel().transform( mCopiedItemsTopLeft );
463 const double deltaX = mLastScenePos.x() - copiedItemsSceneTopLeft.x();
464 const double deltaY = mLastScenePos.y() - copiedItemsSceneTopLeft.y();
465 if ( !mSelectedItems.empty() )
466 {
467 mSelectedItems.clear();
468 }
469
470 for ( std::unique_ptr<QgsAnnotationItem> &copiedItem : mCopiedItems )
471 {
472 QgsAnnotationLayer *annotationLayer = dynamic_cast<QgsAnnotationLayer *>( layer() );
473 if ( !annotationLayer )
474 {
475 annotationLayer = QgsProject::instance()->mainAnnotationLayer();
476 }
477 QString pastedItemId = annotationLayer->addItem( copiedItem->clone() );
478 mSelectedItems.push_back( std::make_unique<QgsAnnotationItemRubberBand>( annotationLayer->id(), pastedItemId, mCanvas ) );
479 attemptMoveBy( mSelectedItems.back().get(), deltaX, deltaY );
480 mSelectedItems.back().get()->setNeedsUpdatedBoundingBox( true );
481 }
483
484 return true;
485 }
486
487 return false;
488}
489
490QList<QgsAnnotationItemRubberBand *> QgsMapToolSelectAnnotation::selectedItems() const
491{
492 QList<QgsAnnotationItemRubberBand *> items;
493 for ( const std::unique_ptr<QgsAnnotationItemRubberBand> &selectedItem : mSelectedItems )
494 {
495 if ( !selectedItem.get()->boundingBox().isEmpty() )
496 {
497 items << selectedItem.get();
498 }
499 }
500 return items;
501}
502
503void QgsMapToolSelectAnnotation::onCanvasRefreshed()
504{
505 const QgsRenderedItemResults *renderedItemResults = canvas()->renderedItemResults( false );
506 if ( !renderedItemResults )
507 {
508 return;
509 }
510
511 const bool interactiveOperation = mMouseHandles && ( mMouseHandles->isDragging() || mMouseHandles->isResizing() || mMouseHandles->isRotating() );
512
513 const QList<QgsRenderedItemDetails *> items = renderedItemResults->renderedItems();
514 bool needsSelectedItemsUpdate = false;
515 for ( std::unique_ptr<QgsAnnotationItemRubberBand> &selectedItem : mSelectedItems )
516 {
517 if ( interactiveOperation && !selectedItem->needsUpdatedBoundingBox() )
518 continue;
519
520 auto it = std::find_if( items.begin(), items.end(), [&selectedItem]( const QgsRenderedItemDetails *item ) {
521 if ( const QgsRenderedAnnotationItemDetails *annotationItem = dynamic_cast<const QgsRenderedAnnotationItemDetails *>( item ) )
522 {
523 if ( annotationItem->itemId() == selectedItem->itemId() && annotationItem->layerId() == selectedItem->layerId() )
524 {
525 return true;
526 }
527 }
528 return false;
529 } );
530
531 if ( it != items.end() )
532 {
533 needsSelectedItemsUpdate = true;
534 selectedItem->updateBoundingBox( ( *it )->boundingBox() );
535 }
536 }
537
538 if ( needsSelectedItemsUpdate )
539 {
540 emit selectedItemsChanged();
541 }
542}
543
544long long QgsMapToolSelectAnnotation::annotationItemRubberBandIndexFromId( const QString &layerId, const QString &itemId )
545{
546 if ( mSelectedItems.empty() )
547 {
548 return -1;
549 }
550
551 auto it = std::find_if( mSelectedItems.begin(), mSelectedItems.end(), [&layerId, &itemId]( auto &item ) { return item->layerId() == layerId && item->itemId() == itemId; } );
552 return it != mSelectedItems.end() ? std::distance( mSelectedItems.begin(), it ) : -1;
553}
554
555void QgsMapToolSelectAnnotation::setSelectedItemsFromRect( const QgsRectangle &mapRect, bool toggleSelection )
556{
557 const QgsRenderedItemResults *renderedItemResults = canvas()->renderedItemResults( false );
558 if ( !renderedItemResults )
559 {
560 if ( !toggleSelection )
561 {
562 clearSelectedItems();
563 }
564 return;
565 }
566
567 const QList<const QgsRenderedAnnotationItemDetails *> items = renderedItemResults->renderedAnnotationItemsInBounds( mapRect );
568 if ( items.empty() )
569 {
570 if ( !toggleSelection )
571 {
572 clearSelectedItems();
573 }
574 return;
575 }
576
577 if ( !toggleSelection )
578 {
579 mSelectedItems.clear();
580 }
581 for ( const QgsRenderedAnnotationItemDetails *item : items )
582 {
583 QgsAnnotationItem *annotationItem = annotationItemFromId( item->layerId(), item->itemId() );
584 if ( annotationItem )
585 {
586 if ( toggleSelection )
587 {
588 long long index = annotationItemRubberBandIndexFromId( item->layerId(), item->itemId() );
589 if ( index >= 0 )
590 {
591 mSelectedItems.erase( mSelectedItems.begin() + index );
592 continue;
593 }
594 }
595 mSelectedItems.push_back( std::make_unique<QgsAnnotationItemRubberBand>( item->layerId(), item->itemId(), mCanvas ) );
596 mSelectedItems.back()->updateBoundingBox( item->boundingBox() );
597 }
598 }
600 updateSelectedItem();
601}
602
603void QgsMapToolSelectAnnotation::setSelectedItemFromPoint( const QgsPointXY &mapPoint, bool toggleSelection )
604{
605 QgsRectangle searchRect = QgsRectangle( mapPoint.x(), mapPoint.y(), mapPoint.x(), mapPoint.y() );
606 searchRect.grow( searchRadiusMU( canvas() ) );
607
608 const QgsRenderedItemResults *renderedItemResults = canvas()->renderedItemResults( false );
609 if ( !renderedItemResults )
610 {
611 clearSelectedItems();
612 return;
613 }
614
615 const QList<const QgsRenderedAnnotationItemDetails *> items = renderedItemResults->renderedAnnotationItemsInBounds( searchRect );
616 if ( items.empty() )
617 {
618 if ( !toggleSelection )
619 {
620 clearSelectedItems();
621 }
622 return;
623 }
624
625 QgsRectangle itemBounds;
626 const QgsRenderedAnnotationItemDetails *closestItem = findClosestItemToPoint( mapPoint, items, itemBounds );
627 if ( !closestItem )
628 {
629 if ( !toggleSelection )
630 {
631 clearSelectedItems();
632 }
633 return;
634 }
635
636 long long index = annotationItemRubberBandIndexFromId( closestItem->layerId(), closestItem->itemId() );
637 if ( index >= 0 )
638 {
639 if ( toggleSelection )
640 {
641 mSelectedItems.erase( mSelectedItems.begin() + index );
642 }
643 }
644 else
645 {
646 if ( !toggleSelection )
647 {
648 mSelectedItems.clear();
649 }
650
651 mSelectedItems.push_back( std::make_unique<QgsAnnotationItemRubberBand>( closestItem->layerId(), closestItem->itemId(), mCanvas ) );
652 mSelectedItems.back()->updateBoundingBox( closestItem->boundingBox() );
653 }
655 updateSelectedItem();
656}
657
658void QgsMapToolSelectAnnotation::updateSelectedItem()
659{
660 if ( mSelectedItems.size() > 1 )
661 {
663 }
664 else if ( mSelectedItems.size() == 1 )
665 {
666 emit singleItemSelected( mSelectedItems[0]->layer(), mSelectedItems[0]->itemId() );
667 }
668 else
669 {
670 emit selectionCleared();
671 }
672}
673
674void QgsMapToolSelectAnnotation::clearSelectedItems()
675{
676 const bool hadSelection = !mSelectedItems.empty();
677 mSelectedItems.clear();
678
679 if ( hadSelection )
680 {
682 updateSelectedItem();
683 }
684}
685
686void QgsMapToolSelectAnnotation::attemptMoveBy( QgsAnnotationItemRubberBand *annotationItemRubberBand, double deltaX, double deltaY )
687{
688 if ( QgsAnnotationItem *annotationItem = annotationItemRubberBand->item() )
689 {
690 QgsRectangle boundingBox = annotationItemRubberBand->boundingBox();
691 // take map rotation into account in translation
692 const QgsMapToPixel *transform = mCanvas->getCoordinateTransform();
693 const QgsPointXY mapOrigin = transform->toMapCoordinates( 0.0, 0.0 );
694 const QgsPointXY mapMoved = transform->toMapCoordinates( deltaX, deltaY );
695 const QgsVector translation( mapMoved.x() - mapOrigin.x(), mapMoved.y() - mapOrigin.y() );
696 QgsAnnotationLayer *annotationLayer = annotationItemRubberBand->layer();
697
699 context.setCurrentItemBounds( boundingBox );
701
702 const QList<QgsAnnotationItemNode> itemNodes = annotationItem->nodesV2( context );
703 for ( const QgsAnnotationItemNode &node : itemNodes )
704 {
705 QgsPointXY mapPoint = mCanvas->mapSettings().layerToMapCoordinates( annotationLayer, node.point() );
706 mapPoint += translation;
707 QgsPointXY modifiedPoint = mCanvas->mapSettings().mapToLayerCoordinates( annotationLayer, mapPoint );
708
709 QgsAnnotationItemEditOperationMoveNode operation( annotationItemRubberBand->itemId(), node.id(), QgsPoint( node.point() ), QgsPoint( modifiedPoint ) );
710 switch ( annotationLayer->applyEditV2( &operation, context ) )
711 {
714 annotationItemRubberBand->setNeedsUpdatedBoundingBox( true );
715 break;
716
719 break;
720 }
721 }
722 boundingBox += translation;
723 annotationItemRubberBand->updateBoundingBox( boundingBox );
724 }
725}
726
727void QgsMapToolSelectAnnotation::attemptRotateBy( QgsAnnotationItemRubberBand *annotationItemRubberBand, double deltaDegree )
728{
729 if ( QgsAnnotationLayer *annotationLayer = annotationItemRubberBand->layer() )
730 {
731 const QgsRectangle boundingBox = mCanvas->mapSettings().mapToLayerCoordinates( annotationLayer, annotationItemRubberBand->boundingBox() );
733 context.setCurrentItemBounds( boundingBox );
735
736 QgsAnnotationItemEditOperationRotateItem operation( annotationItemRubberBand->itemId(), deltaDegree );
737 switch ( annotationLayer->applyEditV2( &operation, context ) )
738 {
741 annotationItemRubberBand->setNeedsUpdatedBoundingBox( true );
742 break;
743
746 break;
747 }
748 }
749}
750
751void QgsMapToolSelectAnnotation::attemptSetSceneRect( QgsAnnotationItemRubberBand *annotationItemRubberBand, const QRectF &rect )
752{
753 QgsAnnotationItem *annotationItem = annotationItemRubberBand->item();
754 if ( !annotationItem )
755 return;
756
757 QgsAnnotationLayer *annotationLayer = annotationItemRubberBand->layer();
758
759 // on-screen rotation = the item's own rotation plus the map rotation when
760 // the item follows it.
761 double rotation = 0;
762 if ( const QgsAnnotationRectItem *rectItem = dynamic_cast<const QgsAnnotationRectItem *>( annotationItem ) )
763 {
764 rotation = rectItem->appliedRotation( mCanvas->mapSettings().rotation() );
765 }
766
767 // Rebuild the unrotated (screen-aligned) rectangle so that, after rotating
768 // around its center, the dragged corner stays where the user placed it.
769 const double w = rect.width();
770 const double h = rect.height();
771 const QPointF halfDiagonal( w / 2.0, h / 2.0 );
772 QTransform itemRotation;
773 itemRotation.rotate( rotation );
774 const QPointF unrotatedTopLeftScene = rect.topLeft() + itemRotation.map( halfDiagonal ) - halfDiagonal;
775 const QRectF newSceneRect( unrotatedTopLeftScene, QSizeF( w, h ) );
776
777 // Derive the bounds from the center and pixel size, which stays correct
778 // when the map is rotated.
779 const QgsMapToPixel *transform = mCanvas->getCoordinateTransform();
780 const QgsRectangle newMapBounds = QgsAnnotationRectItem::boundsFromPixelRect( transform, newSceneRect.center(), w, h );
781
782 const QgsRectangle oldBounds = mCanvas->mapSettings().mapToLayerCoordinates( annotationLayer, annotationItemRubberBand->boundingBox() );
783 const QgsRectangle newBounds = mCanvas->mapSettings().mapToLayerCoordinates( annotationLayer, newMapBounds );
784
785 if ( oldBounds.width() == 0 || oldBounds.height() == 0 )
786 return;
787
789 context.setCurrentItemBounds( oldBounds );
791
792 const QList<QgsAnnotationItemNode> itemNodes = annotationItem->nodesV2( context );
793 for ( const QgsAnnotationItemNode &node : itemNodes )
794 {
795 const double modifiedX = newBounds.xMinimum() + newBounds.width() * ( ( node.point().x() - oldBounds.xMinimum() ) / oldBounds.width() );
796 const double modifiedY = newBounds.yMaximum() - newBounds.height() * ( ( oldBounds.yMaximum() - node.point().y() ) / oldBounds.height() );
797 QgsPointXY modifiedPoint( modifiedX, modifiedY );
798 QgsAnnotationItemEditOperationMoveNode operation( annotationItemRubberBand->itemId(), node.id(), QgsPoint( node.point() ), QgsPoint( modifiedPoint ) );
799 switch ( annotationLayer->applyEditV2( &operation, context ) )
800 {
803 annotationItemRubberBand->setNeedsUpdatedBoundingBox( true );
804 break;
805
808 break;
809 }
810 }
811
812 annotationItemRubberBand->updateBoundingBox( newMapBounds );
813}
Provides global constants and enumerations for use throughout the application.
Definition qgis.h:62
@ ScaleDependentBoundingBox
Item's bounding box will vary depending on map scale.
Definition qgis.h:2652
@ Invalid
Operation has invalid parameters for the item, no change occurred.
Definition qgis.h:2708
@ Success
Item was modified successfully.
Definition qgis.h:2707
@ ItemCleared
The operation results in the item being cleared, and the item should be removed from the layer as a r...
Definition qgis.h:2709
@ Line
Lines.
Definition qgis.h:381
@ Polygon
Polygons.
Definition qgis.h:382
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 moving a node.
Annotation item edit operation consisting of rotating an item.
Contains information about a node used for editing an annotation item.
An annotation item rubberband used by QgsMapToolSelectAnnotation to represent selected items.
bool needsUpdatedBoundingBox() const
Returns true if the bounding box requires updating on fresh annotation item rendering.
QgsRectangle boundingBox() const
Returns the item bounding box.
QString itemId() const
Returns the annotation item ID.
QgsAnnotationItemRubberBand(const QString &layerId, const QString &itemId, QgsMapCanvas *canvas)
Constructor for QgsAnnotationItemRubberBand.
QgsAnnotationItem * item() const
Returns a pointer to the annotation item.
QgsAnnotationLayer * layer() const
Returns a pointer to the annotation layer.
void updateBoundingBox(const QgsRectangle &boundingBox)
Update the rubberband using the provided annotation item bounding box.
QString layerId() const
Returns the annotation layer ID.
void setNeedsUpdatedBoundingBox(bool needsUpdatedBoundingBox)
Sets whether the bounding box requires updating on fresh annotation item rendering.
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.
virtual Qgis::AnnotationItemFlags flags() const
Returns item flags.
Represents a map layer containing a set of georeferenced annotations, e.g.
Qgis::AnnotationItemEditOperationResult applyEditV2(QgsAbstractAnnotationItemEditOperation *operation, const QgsAnnotationItemEditContext &context)
Applies an edit operation to the layer.
QString addItem(QgsAnnotationItem *item)
Adds an item to the layer.
QgsAnnotationItem * item(const QString &id) const
Returns the item with the specified id, or nullptr if no matching item was found.
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.
Abstract base class for annotation items which render annotations in a rectangular shape.
QgsGeometry rotatedBoundsGeometry(const QgsRectangle &layerBounds, const QgsRenderContext &renderContext) const
Returns the polygon geometry (in layer coordinates) for the item occupying the given layerBounds,...
static QgsRectangle boundsFromPixelRect(const QgsMapToPixel *mapToPixel, const QPointF &centerPixel, double widthPixels, double heightPixels)
Returns the axis-aligned bounds, in map coordinates, of a rectangle centered on centerPixel with size...
double appliedRotation(double mapRotation) const
Returns the effective on-screen rotation of the item, in degrees clockwise.
QgsRectangle bounds() const
Returns the bounds of the item.
Handles coordinate transforms between two 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.
QPolygonF asQPolygonF() const
Returns contents of the geometry as a QPolygonF.
QPointF toCanvasCoordinates(const QgsPointXY &point) const
transformation from map coordinates to screen coordinates
QgsMapCanvas * mMapCanvas
pointer to map canvas
QgsMapCanvas * canvas() const
Returns the item's associated canvas.
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 rotationChanged(double rotation)
Emitted when the rotation of the map changes.
void mapCanvasRefreshed()
Emitted when canvas finished a refresh request.
QString id
Definition qgsmaplayer.h:86
A mouse event which is the result of a user interaction with a QgsMapCanvas.
QgsPointXY mapPoint() const
mapPoint returns the point in coordinates
Perform transforms between map coordinates and device coordinates.
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 setAdvancedDigitizingAllowed(bool allowed)
Sets whether functionality of advanced digitizing dock widget is currently allowed.
void activate() override
Registers this maptool with the cad dock widget.
QList< QgsAnnotationItemRubberBand * > selectedItems() const
Returns the current list of selected annotation item rubberband items.
void cadCanvasReleaseEvent(QgsMapMouseEvent *event) override
Override this method when subclassing this class.
void singleItemSelected(QgsAnnotationLayer *layer, const QString &itemId)
Emitted when the selected items list has changed and a single item is selected.
void attemptRotateBy(QgsAnnotationItemRubberBand *annotationItemRubberBand, double deltaDegree)
Attempts to rotate am annotation item.
void keyPressEvent(QKeyEvent *event) override
Key event for overriding. Default implementation does nothing.
void activate() override
Registers this maptool with the cad dock widget.
void attemptSetSceneRect(QgsAnnotationItemRubberBand *annotationItemRubberBand, const QRectF &rect)
Attempts to move and resize an annotation item.
QgsMapToolSelectAnnotation(QgsMapCanvas *canvas, QgsAdvancedDigitizingDockWidget *cadDockWidget)
Constructor for QgsMapToolSelectAnnotation.
void multipleItemsSelected()
Emitted when the selected items list has changed and multiple items are selected.
void cadCanvasMoveEvent(QgsMapMouseEvent *event) override
Override this method when subclassing this class.
void attemptMoveBy(QgsAnnotationItemRubberBand *annotationItemRubberBand, double deltaX, double deltaY)
Attempts to move an annotation item.
void cadCanvasPressEvent(QgsMapMouseEvent *event) override
Override this method when subclassing this class.
void selectedItemsChanged()
Emitted when the selected items list has changed.
void selectionCleared()
Emitted when the selected items list is cleared.
void deactivate() override
Unregisters this maptool from the cad dock widget.
bool shortcutEvent(QKeyEvent *event) override
Shortcut events coming from the application for overriding.
QgsMapCanvas * canvas() const
returns pointer to the tool's map canvas
QgsPointXY toMapCoordinates(QPoint point)
Transforms a point from screen coordinates to map coordinates.
QPointer< QgsMapCanvas > mCanvas
The pointer to the map canvas.
Definition qgsmaptool.h:403
static double searchRadiusMU(const QgsRenderContext &context)
Gets search radius in map units for given context.
Represents a 2D point.
Definition qgspointxy.h:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
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.
double xMinimum
void grow(double delta)
Grows the rectangle in place by the specified amount.
double yMaximum
Contains information about the context of a rendering operation.
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
QString itemId() const
Returns the item ID of the associated annotation 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.
QgsRubberBand(QgsMapCanvas *mapCanvas, Qgis::GeometryType geometryType=Qgis::GeometryType::Line)
Creates a new RubberBand.
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.
void setColor(const QColor &color)
Sets the color for the rubberband.
void addPoint(const QgsPointXY &p, bool doUpdate=true, int geometryIndex=0, int ringIndex=0)
Adds a vertex to the rubberband and update canvas.
Represent a 2-dimensional vector.
Definition qgsvector.h:34
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7557
#define QgsDebugError(str)
Definition qgslogger.h:71
constexpr QObjectUniquePtr< Tp > make_qobject_unique(Args &&...args)
Create an object owned by a QObjectUniquePtr.