33#include <QGraphicsSceneHoverEvent>
34#include <QKeySequence>
39#include "moc_qgsmaptoolselectannotation.cpp"
46 setFlags( flags() | QGraphicsItem::ItemIsSelectable );
48 setWidth( canvas->fontMetrics().xHeight() * .2 );
50 setColor( QColor( 50, 50, 50, 200 ) );
68 return lyr ? lyr->
item( mItemId ) :
nullptr;
104 mSelectionRubberBand.reset();
105 mMouseHandles.reset();
110 mMouseHandles.reset(
new QgsMapToolSelectAnnotationMouseHandles(
this,
mCanvas ) );
117 mSelectionRubberBand.reset();
118 mMouseHandles.reset();
120 mSelectedItems.clear();
122 mCopiedItems.clear();
123 mCopiedItemsTopLeft = QPointF();
130 const QPointF scenePos =
mCanvas->mapToScene( event->pos() );
132 if ( event->buttons() == Qt::NoButton )
134 if ( mMouseHandles->isDragging() )
136 QGraphicsSceneMouseEvent forwardedEvent( QEvent::GraphicsSceneMouseMove );
137 forwardedEvent.setPos( mMouseHandles->mapFromScene( scenePos ) );
138 forwardedEvent.setScenePos( scenePos );
139 forwardedEvent.setLastScenePos( mLastScenePos );
140 forwardedEvent.setButton( Qt::LeftButton );
141 mMouseHandles->mouseMoveEvent( &forwardedEvent );
145 if ( mMouseHandles->sceneBoundingRect().contains( scenePos ) )
147 QGraphicsSceneHoverEvent forwardedEvent( QEvent::GraphicsSceneHoverMove );
148 forwardedEvent.setPos( mMouseHandles->mapFromScene( scenePos ) );
149 forwardedEvent.setScenePos( scenePos );
150 mMouseHandles->hoverMoveEvent( &forwardedEvent );
151 mHoveringMouseHandles =
true;
153 else if ( mHoveringMouseHandles )
155 QGraphicsSceneHoverEvent forwardedEvent( QEvent::GraphicsSceneHoverLeave );
156 forwardedEvent.setPos( mMouseHandles->mapFromScene( scenePos ) );
157 forwardedEvent.setScenePos( scenePos );
158 mMouseHandles->hoverMoveEvent( &forwardedEvent );
159 mHoveringMouseHandles =
false;
163 else if ( event->buttons() == Qt::LeftButton )
165 if ( mMouseHandles->shouldBlockEvent( event ) )
167 QGraphicsSceneMouseEvent forwardedEvent( QEvent::GraphicsSceneMouseMove );
168 forwardedEvent.setPos( mMouseHandles->mapFromScene( scenePos ) );
169 forwardedEvent.setScenePos( scenePos );
170 forwardedEvent.setLastScenePos( mLastScenePos );
171 forwardedEvent.setButton( Qt::LeftButton );
172 mMouseHandles->mouseMoveEvent( &forwardedEvent );
180 QColor color( Qt::blue );
181 color.setAlpha( 63 );
182 mSelectionRubberBand->setColor( color );
183 mSelectionRect.setTopLeft( event->pos() );
186 mSelectionRect.setBottomRight( event->pos() );
187 if ( mSelectionRubberBand )
189 mSelectionRubberBand->setToCanvasRectangle( mSelectionRect );
190 mSelectionRubberBand->show();
195 mLastScenePos = scenePos;
200 if ( event->button() != Qt::LeftButton )
205 QPointF scenePos =
mCanvas->mapToScene( event->pos() );
206 const bool toggleSelection =
event->modifiers() & Qt::ShiftModifier;
208 if ( !toggleSelection && !mSelectedItems.empty() && mMouseHandles->sceneBoundingRect().contains( scenePos ) )
210 QGraphicsSceneMouseEvent forwardedEvent( QEvent::GraphicsSceneMousePress );
211 forwardedEvent.setPos( mMouseHandles->mapFromScene( scenePos ) );
212 forwardedEvent.setScenePos( scenePos );
213 forwardedEvent.setLastScenePos( mLastScenePos );
214 forwardedEvent.setButton( Qt::LeftButton );
215 mMouseHandles->mousePressEvent( &forwardedEvent );
219 mSelectionRect.setTopLeft( event->pos() );
220 mSelectionRect.setBottomRight( event->pos() );
223 mLastScenePos = scenePos;
228 if ( event->button() != Qt::LeftButton )
233 QPointF scenePos =
mCanvas->mapToScene( event->pos() );
235 if ( mMouseHandles->shouldBlockEvent( event ) )
237 QGraphicsSceneMouseEvent forwardedEvent( QEvent::GraphicsSceneMouseRelease );
238 forwardedEvent.setPos( mMouseHandles->mapFromScene( scenePos ) );
239 forwardedEvent.setScenePos( scenePos );
240 forwardedEvent.setLastScenePos( mLastScenePos );
241 forwardedEvent.setButton( Qt::LeftButton );
242 mMouseHandles->mouseReleaseEvent( &forwardedEvent );
256 mSelectionRubberBand.reset();
262 setSelectedItemsFromRect( searchRect, ( event->modifiers() & Qt::ShiftModifier ) );
266 setSelectedItemFromPoint( event->
mapPoint(), ( event->modifiers() & Qt::ShiftModifier ) );
269 mMouseHandles->setSelected( !mSelectedItems.empty() );
275 if ( mMouseHandles->isDragging() || mMouseHandles->isResizing() || mMouseHandles->isRotating() )
280 if ( mSelectedItems.empty() )
285 if ( event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete )
287 while ( !mSelectedItems.empty() )
291 annotationLayer->removeItem( mSelectedItems.back()->itemId() );
293 mSelectedItems.pop_back();
296 updateSelectedItem();
299 else if ( event->key() == Qt::Key_Left
300 || event->key() == Qt::Key_Right
301 || event->key() == Qt::Key_Up
302 || event->key() == Qt::Key_Down )
304 const int pixels = (
event->modifiers() & Qt::ShiftModifier ) ? 1 : 50;
307 if ( event->key() == Qt::Key_Up )
311 else if ( event->key() == Qt::Key_Down )
315 else if ( event->key() == Qt::Key_Left )
319 else if ( event->key() == Qt::Key_Right )
324 for ( std::unique_ptr<QgsAnnotationItemRubberBand> &selectedItem : mSelectedItems )
334 if ( mMouseHandles->isDragging() || mMouseHandles->isResizing() || mMouseHandles->isRotating() )
339 QKeySequence keySequence( event->key() | event->modifiers() );
341 if ( keySequence == QKeySequence::Copy || keySequence == QKeySequence::Cut )
343 mCopiedItems.clear();
344 mCopiedItemsTopLeft =
toMapCoordinates( QPoint( mMouseHandles->sceneBoundingRect().topLeft().x(), mMouseHandles->sceneBoundingRect().topLeft().y() ) );
345 for ( std::unique_ptr<QgsAnnotationItemRubberBand> &selectedItem : mSelectedItems )
349 std::unique_ptr<QgsAnnotationItem> copiedItem;
350 copiedItem.reset( annotationItem->clone() );
351 mCopiedItems.push_back( std::move( copiedItem ) );
355 if ( keySequence == QKeySequence::Cut )
357 while ( !mSelectedItems.empty() )
361 annotationLayer->removeItem( mSelectedItems.back()->itemId() );
363 mSelectedItems.pop_back();
370 else if ( keySequence == QKeySequence::Paste )
372 const QgsPointXY copiedItemsSceneTopLeft =
mCanvas->mapSettings().mapToPixel().transform( mCopiedItemsTopLeft );
373 const double deltaX = mLastScenePos.x() - copiedItemsSceneTopLeft.
x();
374 const double deltaY = mLastScenePos.y() - copiedItemsSceneTopLeft.
y();
375 if ( !mSelectedItems.empty() )
377 mSelectedItems.clear();
380 for ( std::unique_ptr<QgsAnnotationItem> &copiedItem : mCopiedItems )
383 if ( !annotationLayer )
387 QString pastedItemId = annotationLayer->
addItem( copiedItem->clone() );
388 mSelectedItems.push_back( std::make_unique<QgsAnnotationItemRubberBand>( annotationLayer->
id(), pastedItemId,
mCanvas ) );
389 attemptMoveBy( mSelectedItems.back().get(), deltaX, deltaY );
390 mSelectedItems.back().get()->setNeedsUpdatedBoundingBox(
true );
402 QList<QgsAnnotationItemRubberBand *> items;
403 for (
const std::unique_ptr<QgsAnnotationItemRubberBand> &selectedItem : mSelectedItems )
405 if ( !selectedItem.get()->boundingBox().isEmpty() )
407 items << selectedItem.get();
413void QgsMapToolSelectAnnotation::onCanvasRefreshed()
416 if ( !renderedItemResults )
421 const QList<QgsRenderedItemDetails *> items = renderedItemResults->
renderedItems();
422 bool needsSelectedItemsUpdate =
false;
423 for ( std::unique_ptr<QgsAnnotationItemRubberBand> &selectedItem : mSelectedItems )
425 if ( selectedItem->needsUpdatedBoundingBox() )
427 needsSelectedItemsUpdate =
true;
429 auto it = std::find_if( items.begin(), items.end(), [&selectedItem](
const QgsRenderedItemDetails *item ) {
430 if ( const QgsRenderedAnnotationItemDetails *annotationItem = dynamic_cast<const QgsRenderedAnnotationItemDetails *>( item ) )
432 if ( annotationItem->itemId() == selectedItem->itemId() && annotationItem->layerId() == selectedItem->layerId() )
440 if ( it != items.end() )
442 selectedItem->updateBoundingBox( ( *it )->boundingBox() );
447 if ( needsSelectedItemsUpdate )
449 emit selectedItemsChanged();
453long long QgsMapToolSelectAnnotation::annotationItemRubberBandIndexFromId(
const QString &layerId,
const QString &itemId )
455 if ( mSelectedItems.empty() )
460 auto it = std::find_if( mSelectedItems.begin(), mSelectedItems.end(), [&layerId, &itemId](
auto &item ) { return item->layerId() == layerId && item->itemId() == itemId; } );
461 return it != mSelectedItems.end() ? std::distance( mSelectedItems.begin(), it ) : -1;
464void QgsMapToolSelectAnnotation::setSelectedItemsFromRect(
const QgsRectangle &mapRect,
bool toggleSelection )
467 if ( !renderedItemResults )
469 if ( !toggleSelection )
471 clearSelectedItems();
479 if ( !toggleSelection )
481 clearSelectedItems();
486 if ( !toggleSelection )
488 mSelectedItems.clear();
490 for (
const QgsRenderedAnnotationItemDetails *item : items )
493 if ( annotationItem )
495 if ( toggleSelection )
497 long long index = annotationItemRubberBandIndexFromId( item->layerId(), item->itemId() );
500 mSelectedItems.erase( mSelectedItems.begin() + index );
504 mSelectedItems.push_back( std::make_unique<QgsAnnotationItemRubberBand>( item->layerId(), item->itemId(),
mCanvas ) );
505 mSelectedItems.back()->updateBoundingBox( item->boundingBox() );
509 updateSelectedItem();
512void QgsMapToolSelectAnnotation::setSelectedItemFromPoint(
const QgsPointXY &mapPoint,
bool toggleSelection )
514 QgsRectangle searchRect = QgsRectangle( mapPoint.
x(), mapPoint.
y(), mapPoint.
x(), mapPoint.
y() );
518 if ( !renderedItemResults )
520 clearSelectedItems();
527 if ( !toggleSelection )
529 clearSelectedItems();
534 QgsRectangle itemBounds;
535 const QgsRenderedAnnotationItemDetails *closestItem =
findClosestItemToPoint( mapPoint, items, itemBounds );
538 if ( !toggleSelection )
540 clearSelectedItems();
545 long long index = annotationItemRubberBandIndexFromId( closestItem->
layerId(), closestItem->
itemId() );
548 if ( toggleSelection )
550 mSelectedItems.erase( mSelectedItems.begin() + index );
555 if ( !toggleSelection )
557 mSelectedItems.clear();
560 mSelectedItems.push_back( std::make_unique<QgsAnnotationItemRubberBand>( closestItem->
layerId(), closestItem->
itemId(),
mCanvas ) );
561 mSelectedItems.back()->updateBoundingBox( closestItem->
boundingBox() );
564 updateSelectedItem();
567void QgsMapToolSelectAnnotation::updateSelectedItem()
569 if ( mSelectedItems.size() > 1 )
573 else if ( mSelectedItems.size() == 1 )
583void QgsMapToolSelectAnnotation::clearSelectedItems()
585 const bool hadSelection = !mSelectedItems.empty();
586 mSelectedItems.clear();
591 updateSelectedItem();
600 const double mupp =
mCanvas->mapSettings().mapUnitsPerPixel();
601 QgsVector translation( deltaX * mupp, -deltaY * mupp );
608 const QList<QgsAnnotationItemNode> itemNodes = annotationItem->
nodesV2( context );
611 QgsPointXY mapPoint =
mCanvas->mapSettings().layerToMapCoordinates( annotationLayer, node.point() );
612 mapPoint += translation;
613 QgsPointXY modifiedPoint =
mCanvas->mapSettings().mapToLayerCoordinates( annotationLayer, mapPoint );
616 switch ( annotationLayer->
applyEditV2( &operation, context ) )
628 boundingBox += translation;
643 switch ( annotationLayer->applyEditV2( &operation, context ) )
661 const double widthRatio = rect.width() / annotationItemRubberBand->
boundingRect().width();
662 const double heightRatio = rect.height() / annotationItemRubberBand->
boundingRect().height();
663 const double deltaX = rect.x() - annotationItemRubberBand->x() + 1;
664 const double deltaY = rect.y() - annotationItemRubberBand->y() + 1;
675 const QList<QgsAnnotationItemNode> itemNodes = annotationItem->
nodesV2( context );
678 const double modifiedX = modifiedBoundingBox.
xMinimum() + modifiedBoundingBox.
width() * ( ( node.point().x() - boundingBox.
xMinimum() ) / boundingBox.
width() );
679 const double modifiedY = modifiedBoundingBox.
yMaximum() - modifiedBoundingBox.
height() * ( ( boundingBox.
yMaximum() - node.point().y() ) / boundingBox.
height() );
680 QgsPointXY modifiedPoint( modifiedX, modifiedY );
682 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.
QRectF boundingRect() const override
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.
A mouse event which is the result of a user interaction with a QgsMapCanvas.
QgsPointXY mapPoint() const
mapPoint returns the point in 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.
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.
Responsible for drawing transient features (e.g.
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.