42#include <QGraphicsSceneHoverEvent>
43#include <QKeySequence>
49#include "moc_qgsmaptoolselectannotation.cpp"
51using namespace Qt::StringLiterals;
58 setFlags( flags() | QGraphicsItem::ItemIsSelectable );
62 setColor( QColor( 50, 50, 50, 200 ) );
80 return lyr ? lyr->
item( mItemId ) :
nullptr;
94 const double mapRotation =
mMapCanvas->mapSettings().rotation();
95 const double frameRotation = rectItem ? rectItem->
appliedRotation( mapRotation ) - mapRotation : 0;
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() );
116 for (
const QPointF &corner : corners )
126 QgsDebugError( u
"Error transforming annotation item corner"_s );
140 for (
const QgsPointXY &corner : bboxCorners )
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;
188 for ( std::unique_ptr<QgsAnnotationItemRubberBand> &selectedItem : mSelectedItems )
189 selectedItem->updateBoundingBox( selectedItem->boundingBox() );
197 mSelectionRubberBand.reset();
198 mMouseHandles.reset();
210 mSelectionRubberBand.reset();
211 mMouseHandles.reset();
213 mSelectedItems.clear();
215 mCopiedItems.clear();
216 mCopiedItemsTopLeft = QPointF();
223 const QPointF scenePos =
mCanvas->mapToScene( event->pos() );
225 if ( event->buttons() == Qt::NoButton )
227 if ( mMouseHandles->isDragging() )
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 );
238 if ( mMouseHandles->sceneBoundingRect().contains( scenePos ) )
240 QGraphicsSceneHoverEvent forwardedEvent( QEvent::GraphicsSceneHoverMove );
241 forwardedEvent.setPos( mMouseHandles->mapFromScene( scenePos ) );
242 forwardedEvent.setScenePos( scenePos );
243 mMouseHandles->hoverMoveEvent( &forwardedEvent );
244 mHoveringMouseHandles =
true;
246 else if ( mHoveringMouseHandles )
248 QGraphicsSceneHoverEvent forwardedEvent( QEvent::GraphicsSceneHoverLeave );
249 forwardedEvent.setPos( mMouseHandles->mapFromScene( scenePos ) );
250 forwardedEvent.setScenePos( scenePos );
251 mMouseHandles->hoverMoveEvent( &forwardedEvent );
252 mHoveringMouseHandles =
false;
256 else if ( event->buttons() == Qt::LeftButton )
258 if ( mMouseHandles->shouldBlockEvent( event ) )
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 );
273 QColor color( Qt::blue );
274 color.setAlpha( 63 );
275 mSelectionRubberBand->setColor( color );
276 mSelectionRect.setTopLeft( event->pos() );
279 mSelectionRect.setBottomRight( event->pos() );
280 if ( mSelectionRubberBand )
282 mSelectionRubberBand->setToCanvasRectangle( mSelectionRect );
283 mSelectionRubberBand->show();
288 mLastScenePos = scenePos;
293 if ( event->button() != Qt::LeftButton )
298 QPointF scenePos =
mCanvas->mapToScene( event->pos() );
299 const bool toggleSelection =
event->modifiers() & Qt::ShiftModifier;
301 if ( !toggleSelection && !mSelectedItems.empty() && mMouseHandles->sceneBoundingRect().contains( scenePos ) )
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 );
312 mSelectionRect.setTopLeft( event->pos() );
313 mSelectionRect.setBottomRight( event->pos() );
316 mLastScenePos = scenePos;
321 if ( event->button() != Qt::LeftButton )
326 QPointF scenePos =
mCanvas->mapToScene( event->pos() );
328 if ( mMouseHandles->shouldBlockEvent( event ) )
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 );
349 mSelectionRubberBand.reset();
355 setSelectedItemsFromRect( searchRect, ( event->modifiers() & Qt::ShiftModifier ) );
359 setSelectedItemFromPoint( event->
mapPoint(), ( event->modifiers() & Qt::ShiftModifier ) );
362 mMouseHandles->setSelected( !mSelectedItems.empty() );
368 if ( mMouseHandles->isDragging() || mMouseHandles->isResizing() || mMouseHandles->isRotating() )
373 if ( mSelectedItems.empty() )
378 if ( event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete )
380 while ( !mSelectedItems.empty() )
384 annotationLayer->removeItem( mSelectedItems.back()->itemId() );
386 mSelectedItems.pop_back();
389 updateSelectedItem();
392 else if ( event->key() == Qt::Key_Left || event->key() == Qt::Key_Right || event->key() == Qt::Key_Up || event->key() == Qt::Key_Down )
394 const int pixels = (
event->modifiers() & Qt::ShiftModifier ) ? 1 : 50;
397 if ( event->key() == Qt::Key_Up )
401 else if ( event->key() == Qt::Key_Down )
405 else if ( event->key() == Qt::Key_Left )
409 else if ( event->key() == Qt::Key_Right )
414 for ( std::unique_ptr<QgsAnnotationItemRubberBand> &selectedItem : mSelectedItems )
424 if ( mMouseHandles->isDragging() || mMouseHandles->isResizing() || mMouseHandles->isRotating() )
429 QKeySequence keySequence( event->key() | event->modifiers() );
431 if ( keySequence == QKeySequence::Copy || keySequence == QKeySequence::Cut )
433 mCopiedItems.clear();
434 mCopiedItemsTopLeft =
toMapCoordinates( QPoint( mMouseHandles->sceneBoundingRect().topLeft().x(), mMouseHandles->sceneBoundingRect().topLeft().y() ) );
435 for ( std::unique_ptr<QgsAnnotationItemRubberBand> &selectedItem : mSelectedItems )
439 std::unique_ptr<QgsAnnotationItem> copiedItem;
440 copiedItem.reset( annotationItem->clone() );
441 mCopiedItems.push_back( std::move( copiedItem ) );
445 if ( keySequence == QKeySequence::Cut )
447 while ( !mSelectedItems.empty() )
451 annotationLayer->removeItem( mSelectedItems.back()->itemId() );
453 mSelectedItems.pop_back();
460 else if ( keySequence == QKeySequence::Paste )
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() )
467 mSelectedItems.clear();
470 for ( std::unique_ptr<QgsAnnotationItem> &copiedItem : mCopiedItems )
473 if ( !annotationLayer )
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 );
492 QList<QgsAnnotationItemRubberBand *> items;
493 for (
const std::unique_ptr<QgsAnnotationItemRubberBand> &selectedItem : mSelectedItems )
495 if ( !selectedItem.get()->boundingBox().isEmpty() )
497 items << selectedItem.get();
503void QgsMapToolSelectAnnotation::onCanvasRefreshed()
506 if ( !renderedItemResults )
511 const bool interactiveOperation = mMouseHandles && ( mMouseHandles->isDragging() || mMouseHandles->isResizing() || mMouseHandles->isRotating() );
513 const QList<QgsRenderedItemDetails *> items = renderedItemResults->
renderedItems();
514 bool needsSelectedItemsUpdate =
false;
515 for ( std::unique_ptr<QgsAnnotationItemRubberBand> &selectedItem : mSelectedItems )
517 if ( interactiveOperation && !selectedItem->needsUpdatedBoundingBox() )
520 auto it = std::find_if( items.begin(), items.end(), [&selectedItem](
const QgsRenderedItemDetails *item ) {
521 if ( const QgsRenderedAnnotationItemDetails *annotationItem = dynamic_cast<const QgsRenderedAnnotationItemDetails *>( item ) )
523 if ( annotationItem->itemId() == selectedItem->itemId() && annotationItem->layerId() == selectedItem->layerId() )
531 if ( it != items.end() )
533 needsSelectedItemsUpdate =
true;
534 selectedItem->updateBoundingBox( ( *it )->boundingBox() );
538 if ( needsSelectedItemsUpdate )
540 emit selectedItemsChanged();
544long long QgsMapToolSelectAnnotation::annotationItemRubberBandIndexFromId(
const QString &layerId,
const QString &itemId )
546 if ( mSelectedItems.empty() )
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;
555void QgsMapToolSelectAnnotation::setSelectedItemsFromRect(
const QgsRectangle &mapRect,
bool toggleSelection )
558 if ( !renderedItemResults )
560 if ( !toggleSelection )
562 clearSelectedItems();
570 if ( !toggleSelection )
572 clearSelectedItems();
577 if ( !toggleSelection )
579 mSelectedItems.clear();
581 for (
const QgsRenderedAnnotationItemDetails *item : items )
584 if ( annotationItem )
586 if ( toggleSelection )
588 long long index = annotationItemRubberBandIndexFromId( item->layerId(), item->itemId() );
591 mSelectedItems.erase( mSelectedItems.begin() + index );
595 mSelectedItems.push_back( std::make_unique<QgsAnnotationItemRubberBand>( item->layerId(), item->itemId(),
mCanvas ) );
596 mSelectedItems.back()->updateBoundingBox( item->boundingBox() );
600 updateSelectedItem();
603void QgsMapToolSelectAnnotation::setSelectedItemFromPoint(
const QgsPointXY &mapPoint,
bool toggleSelection )
605 QgsRectangle searchRect = QgsRectangle( mapPoint.
x(), mapPoint.
y(), mapPoint.
x(), mapPoint.
y() );
609 if ( !renderedItemResults )
611 clearSelectedItems();
618 if ( !toggleSelection )
620 clearSelectedItems();
625 QgsRectangle itemBounds;
626 const QgsRenderedAnnotationItemDetails *closestItem =
findClosestItemToPoint( mapPoint, items, itemBounds );
629 if ( !toggleSelection )
631 clearSelectedItems();
636 long long index = annotationItemRubberBandIndexFromId( closestItem->
layerId(), closestItem->
itemId() );
639 if ( toggleSelection )
641 mSelectedItems.erase( mSelectedItems.begin() + index );
646 if ( !toggleSelection )
648 mSelectedItems.clear();
651 mSelectedItems.push_back( std::make_unique<QgsAnnotationItemRubberBand>( closestItem->
layerId(), closestItem->
itemId(),
mCanvas ) );
652 mSelectedItems.back()->updateBoundingBox( closestItem->
boundingBox() );
655 updateSelectedItem();
658void QgsMapToolSelectAnnotation::updateSelectedItem()
660 if ( mSelectedItems.size() > 1 )
664 else if ( mSelectedItems.size() == 1 )
674void QgsMapToolSelectAnnotation::clearSelectedItems()
676 const bool hadSelection = !mSelectedItems.empty();
677 mSelectedItems.clear();
682 updateSelectedItem();
695 const QgsVector translation( mapMoved.
x() - mapOrigin.
x(), mapMoved.
y() - mapOrigin.
y() );
702 const QList<QgsAnnotationItemNode> itemNodes = annotationItem->
nodesV2( context );
705 QgsPointXY mapPoint =
mCanvas->mapSettings().layerToMapCoordinates( annotationLayer, node.point() );
706 mapPoint += translation;
707 QgsPointXY modifiedPoint =
mCanvas->mapSettings().mapToLayerCoordinates( annotationLayer, mapPoint );
710 switch ( annotationLayer->
applyEditV2( &operation, context ) )
722 boundingBox += translation;
737 switch ( annotationLayer->applyEditV2( &operation, context ) )
754 if ( !annotationItem )
764 rotation = rectItem->appliedRotation(
mCanvas->mapSettings().rotation() );
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 ) );
783 const QgsRectangle newBounds =
mCanvas->mapSettings().mapToLayerCoordinates( annotationLayer, newMapBounds );
785 if ( oldBounds.
width() == 0 || oldBounds.
height() == 0 )
792 const QList<QgsAnnotationItemNode> itemNodes = annotationItem->
nodesV2( context );
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 );
799 switch ( annotationLayer->
applyEditV2( &operation, context ) )
Provides global constants and enumerations for use throughout the application.
@ 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...
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.
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 ¢erPixel, 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.
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.
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.
Point geometry type, with support for z-dimension and m-values.
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.
void grow(double delta)
Grows the rectangle in place by the specified amount.
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.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
#define QgsDebugError(str)
constexpr QObjectUniquePtr< Tp > make_qobject_unique(Args &&...args)
Create an object owned by a QObjectUniquePtr.