33#include <QGraphicsSceneHoverEvent>
38#include "moc_qgsmaptoolselectannotation.cpp"
45 setFlags( flags() | QGraphicsItem::ItemIsSelectable );
47 setWidth( canvas->fontMetrics().xHeight() * .2 );
49 setColor( QColor( 50, 50, 50, 200 ) );
66 return lyr ? lyr->
item( mItemId ) :
nullptr;
102 mSelectionRubberBand.reset();
103 mMouseHandles.reset();
108 mMouseHandles.reset(
new QgsMapToolSelectAnnotationMouseHandles(
this,
mCanvas ) );
115 mSelectionRubberBand.reset();
116 mMouseHandles.reset();
118 mSelectedItems.clear();
120 mCopiedItems.clear();
121 mCopiedItemsTopLeft = QPointF();
128 const QPointF scenePos =
mCanvas->mapToScene( event->pos() );
130 if ( event->buttons() == Qt::NoButton )
132 if ( mMouseHandles->sceneBoundingRect().contains( scenePos ) )
134 QGraphicsSceneHoverEvent forwardedEvent( QEvent::GraphicsSceneHoverMove );
135 forwardedEvent.setPos( mMouseHandles->mapFromScene( scenePos ) );
136 forwardedEvent.setScenePos( scenePos );
137 mMouseHandles->hoverMoveEvent( &forwardedEvent );
138 mHoveringMouseHandles =
true;
140 else if ( mHoveringMouseHandles )
142 QGraphicsSceneHoverEvent forwardedEvent( QEvent::GraphicsSceneHoverLeave );
143 forwardedEvent.setPos( mMouseHandles->mapFromScene( scenePos ) );
144 forwardedEvent.setScenePos( scenePos );
145 mMouseHandles->hoverMoveEvent( &forwardedEvent );
146 mHoveringMouseHandles =
false;
149 else if ( event->buttons() == Qt::LeftButton )
151 if ( mMouseHandles->shouldBlockEvent( event ) )
153 QGraphicsSceneMouseEvent forwardedEvent( QEvent::GraphicsSceneMouseMove );
154 forwardedEvent.setPos( mMouseHandles->mapFromScene( scenePos ) );
155 forwardedEvent.setScenePos( scenePos );
156 forwardedEvent.setLastScenePos( mLastScenePos );
157 forwardedEvent.setButton( Qt::LeftButton );
158 mMouseHandles->mouseMoveEvent( &forwardedEvent );
166 QColor color( Qt::blue );
167 color.setAlpha( 63 );
168 mSelectionRubberBand->setColor( color );
169 mSelectionRect.setTopLeft( event->pos() );
172 mSelectionRect.setBottomRight( event->pos() );
173 if ( mSelectionRubberBand )
175 mSelectionRubberBand->setToCanvasRectangle( mSelectionRect );
176 mSelectionRubberBand->show();
181 mLastScenePos = scenePos;
186 if ( event->button() != Qt::LeftButton )
191 QPointF scenePos =
mCanvas->mapToScene( event->pos() );
192 const bool toggleSelection =
event->modifiers() & Qt::ShiftModifier;
194 if ( !toggleSelection && !mSelectedItems.empty() && mMouseHandles->sceneBoundingRect().contains( scenePos ) )
196 QGraphicsSceneMouseEvent forwardedEvent( QEvent::GraphicsSceneMousePress );
197 forwardedEvent.setPos( mMouseHandles->mapFromScene( scenePos ) );
198 forwardedEvent.setScenePos( scenePos );
199 forwardedEvent.setLastScenePos( mLastScenePos );
200 forwardedEvent.setButton( Qt::LeftButton );
201 mMouseHandles->mousePressEvent( &forwardedEvent );
205 mSelectionRect.setTopLeft( event->pos() );
206 mSelectionRect.setBottomRight( event->pos() );
209 mLastScenePos = scenePos;
214 if ( event->button() != Qt::LeftButton )
219 QPointF scenePos =
mCanvas->mapToScene( event->pos() );
221 if ( mMouseHandles->shouldBlockEvent( event ) )
223 QGraphicsSceneMouseEvent forwardedEvent( QEvent::GraphicsSceneMouseRelease );
224 forwardedEvent.setPos( mMouseHandles->mapFromScene( scenePos ) );
225 forwardedEvent.setScenePos( scenePos );
226 forwardedEvent.setLastScenePos( mLastScenePos );
227 forwardedEvent.setButton( Qt::LeftButton );
228 mMouseHandles->mouseReleaseEvent( &forwardedEvent );
242 mSelectionRubberBand.reset();
248 setSelectedItemsFromRect( searchRect, ( event->modifiers() & Qt::ShiftModifier ) );
252 setSelectedItemFromPoint( event->
mapPoint(), ( event->modifiers() & Qt::ShiftModifier ) );
255 mMouseHandles->setSelected( !mSelectedItems.empty() );
261 if ( mMouseHandles->isDragging() || mMouseHandles->isResizing() || mMouseHandles->isRotating() )
266 if ( mSelectedItems.empty() )
271 if ( event->key() == Qt::Key_C || event->key() == Qt::Key_X )
273 mCopiedItems.clear();
274 mCopiedItemsTopLeft =
toMapCoordinates( QPoint( mMouseHandles->sceneBoundingRect().topLeft().x(), mMouseHandles->sceneBoundingRect().topLeft().y() ) );
275 for ( std::unique_ptr<QgsAnnotationItemRubberBand> &selectedItem : mSelectedItems )
277 mCopiedItems << qMakePair( selectedItem->layerId(), selectedItem->itemId() );
279 if ( event->key() == Qt::Key_C )
285 else if ( event->key() == Qt::Key_V )
287 const QgsPointXY copiedItemsSceneTopLeft =
mCanvas->mapSettings().mapToPixel().transform( mCopiedItemsTopLeft );
288 const double deltaX = mLastScenePos.x() - copiedItemsSceneTopLeft.
x();
289 const double deltaY = mLastScenePos.y() - copiedItemsSceneTopLeft.
y();
290 if ( !mSelectedItems.empty() )
292 mSelectedItems.clear();
295 for (
const QPair<QString, QString> &copiedItem : mCopiedItems )
300 if ( !annotationLayer )
304 QString pastedItemId = annotationLayer->
addItem( annotationItem->clone() );
306 mSelectedItems.push_back( std::make_unique<QgsAnnotationItemRubberBand>( annotationLayer->
id(), pastedItemId,
mCanvas ) );
307 attemptMoveBy( mSelectedItems.back().get(), deltaX, deltaY );
311 updateSelectedItem();
316 if ( event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete || event->key() == Qt::Key_X )
318 while ( !mSelectedItems.empty() )
322 annotationLayer->removeItem( mSelectedItems.back()->itemId() );
324 mSelectedItems.pop_back();
327 updateSelectedItem();
330 else if ( event->key() == Qt::Key_Left
331 || event->key() == Qt::Key_Right
332 || event->key() == Qt::Key_Up
333 || event->key() == Qt::Key_Down )
335 const int pixels = (
event->modifiers() & Qt::ShiftModifier ) ? 1 : 50;
338 if ( event->key() == Qt::Key_Up )
342 else if ( event->key() == Qt::Key_Down )
346 else if ( event->key() == Qt::Key_Left )
350 else if ( event->key() == Qt::Key_Right )
355 for ( std::unique_ptr<QgsAnnotationItemRubberBand> &selectedItem : mSelectedItems )
365 QList<QgsAnnotationItemRubberBand *> items;
366 for (
const std::unique_ptr<QgsAnnotationItemRubberBand> &selectedItem : mSelectedItems )
368 items << selectedItem.get();
373void QgsMapToolSelectAnnotation::onCanvasRefreshed()
376 if ( !renderedItemResults )
381 const QList<QgsRenderedItemDetails *> items = renderedItemResults->
renderedItems();
382 bool needsSelectedItemsUpdate =
false;
383 for ( std::unique_ptr<QgsAnnotationItemRubberBand> &selectedItem : mSelectedItems )
385 if ( selectedItem->needsUpdatedBoundingBox() )
387 needsSelectedItemsUpdate =
true;
389 auto it = std::find_if( items.begin(), items.end(), [&selectedItem](
const QgsRenderedItemDetails *item ) {
390 if ( const QgsRenderedAnnotationItemDetails *annotationItem = dynamic_cast<const QgsRenderedAnnotationItemDetails *>( item ) )
392 if ( annotationItem->itemId() == selectedItem->itemId() && annotationItem->layerId() == selectedItem->layerId() )
400 if ( it != items.end() )
402 selectedItem->updateBoundingBox( ( *it )->boundingBox() );
407 if ( needsSelectedItemsUpdate )
409 emit selectedItemsChanged();
413long long QgsMapToolSelectAnnotation::annotationItemRubberBandIndexFromId(
const QString &layerId,
const QString &itemId )
415 if ( mSelectedItems.empty() )
420 auto it = std::find_if( mSelectedItems.begin(), mSelectedItems.end(), [&layerId, &itemId](
auto &item ) { return item->layerId() == layerId && item->itemId() == itemId; } );
421 return it != mSelectedItems.end() ? std::distance( mSelectedItems.begin(), it ) : -1;
424void QgsMapToolSelectAnnotation::setSelectedItemsFromRect(
const QgsRectangle &mapRect,
bool toggleSelection )
427 if ( !renderedItemResults )
429 if ( !toggleSelection )
431 clearSelectedItems();
439 if ( !toggleSelection )
441 clearSelectedItems();
446 if ( !toggleSelection )
448 mSelectedItems.clear();
450 for (
const QgsRenderedAnnotationItemDetails *item : items )
453 if ( annotationItem )
455 if ( toggleSelection )
457 long long index = annotationItemRubberBandIndexFromId( item->layerId(), item->itemId() );
460 mSelectedItems.erase( mSelectedItems.begin() + index );
464 mSelectedItems.push_back( std::make_unique<QgsAnnotationItemRubberBand>( item->layerId(), item->itemId(),
mCanvas ) );
465 mSelectedItems.back()->updateBoundingBox( item->boundingBox() );
469 updateSelectedItem();
472void QgsMapToolSelectAnnotation::setSelectedItemFromPoint(
const QgsPointXY &mapPoint,
bool toggleSelection )
474 QgsRectangle searchRect = QgsRectangle( mapPoint.
x(), mapPoint.
y(), mapPoint.
x(), mapPoint.
y() );
478 if ( !renderedItemResults )
480 clearSelectedItems();
487 if ( !toggleSelection )
489 clearSelectedItems();
494 QgsRectangle itemBounds;
495 const QgsRenderedAnnotationItemDetails *closestItem =
findClosestItemToPoint( mapPoint, items, itemBounds );
498 if ( !toggleSelection )
500 clearSelectedItems();
505 long long index = annotationItemRubberBandIndexFromId( closestItem->
layerId(), closestItem->
itemId() );
508 if ( toggleSelection )
510 mSelectedItems.erase( mSelectedItems.begin() + index );
515 if ( !toggleSelection )
517 mSelectedItems.clear();
520 mSelectedItems.push_back( std::make_unique<QgsAnnotationItemRubberBand>( closestItem->
layerId(), closestItem->
itemId(),
mCanvas ) );
521 mSelectedItems.back()->updateBoundingBox( closestItem->
boundingBox() );
524 updateSelectedItem();
527void QgsMapToolSelectAnnotation::updateSelectedItem()
529 if ( mSelectedItems.size() > 1 )
533 else if ( mSelectedItems.size() == 1 )
543void QgsMapToolSelectAnnotation::clearSelectedItems()
545 const bool hadSelection = !mSelectedItems.empty();
546 mSelectedItems.clear();
551 updateSelectedItem();
560 const double mupp =
mCanvas->mapSettings().mapUnitsPerPixel();
561 QgsVector translation( deltaX * mupp, -deltaY * mupp );
568 const QList<QgsAnnotationItemNode> itemNodes = annotationItem->
nodesV2( context );
571 QgsPointXY mapPoint =
mCanvas->mapSettings().layerToMapCoordinates( annotationLayer, node.point() );
572 mapPoint += translation;
573 QgsPointXY modifiedPoint =
mCanvas->mapSettings().mapToLayerCoordinates( annotationLayer, mapPoint );
576 switch ( annotationLayer->
applyEditV2( &operation, context ) )
588 boundingBox += translation;
603 switch ( annotationLayer->applyEditV2( &operation, context ) )
621 const double widthRatio = rect.width() / annotationItemRubberBand->
boundingRect().width();
622 const double heightRatio = rect.height() / annotationItemRubberBand->
boundingRect().height();
623 const double deltaX = rect.x() - annotationItemRubberBand->x() + 1;
624 const double deltaY = rect.y() - annotationItemRubberBand->y() + 1;
635 const QList<QgsAnnotationItemNode> itemNodes = annotationItem->
nodesV2( context );
638 const double modifiedX = modifiedBoundingBox.
xMinimum() + modifiedBoundingBox.
width() * ( ( node.point().x() - boundingBox.
xMinimum() ) / boundingBox.
width() );
639 const double modifiedY = modifiedBoundingBox.
yMaximum() - modifiedBoundingBox.
height() * ( ( boundingBox.
yMaximum() - node.point().y() ) / boundingBox.
height() );
640 QgsPointXY modifiedPoint( modifiedX, modifiedY );
642 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.