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->isDragging() )
134 QGraphicsSceneMouseEvent forwardedEvent( QEvent::GraphicsSceneMouseMove );
135 forwardedEvent.setPos( mMouseHandles->mapFromScene( scenePos ) );
136 forwardedEvent.setScenePos( scenePos );
137 forwardedEvent.setLastScenePos( mLastScenePos );
138 forwardedEvent.setButton( Qt::LeftButton );
139 mMouseHandles->mouseMoveEvent( &forwardedEvent );
143 if ( mMouseHandles->sceneBoundingRect().contains( scenePos ) )
145 QGraphicsSceneHoverEvent forwardedEvent( QEvent::GraphicsSceneHoverMove );
146 forwardedEvent.setPos( mMouseHandles->mapFromScene( scenePos ) );
147 forwardedEvent.setScenePos( scenePos );
148 mMouseHandles->hoverMoveEvent( &forwardedEvent );
149 mHoveringMouseHandles =
true;
151 else if ( mHoveringMouseHandles )
153 QGraphicsSceneHoverEvent forwardedEvent( QEvent::GraphicsSceneHoverLeave );
154 forwardedEvent.setPos( mMouseHandles->mapFromScene( scenePos ) );
155 forwardedEvent.setScenePos( scenePos );
156 mMouseHandles->hoverMoveEvent( &forwardedEvent );
157 mHoveringMouseHandles =
false;
161 else if ( event->buttons() == Qt::LeftButton )
163 if ( mMouseHandles->shouldBlockEvent( event ) )
165 QGraphicsSceneMouseEvent forwardedEvent( QEvent::GraphicsSceneMouseMove );
166 forwardedEvent.setPos( mMouseHandles->mapFromScene( scenePos ) );
167 forwardedEvent.setScenePos( scenePos );
168 forwardedEvent.setLastScenePos( mLastScenePos );
169 forwardedEvent.setButton( Qt::LeftButton );
170 mMouseHandles->mouseMoveEvent( &forwardedEvent );
178 QColor color( Qt::blue );
179 color.setAlpha( 63 );
180 mSelectionRubberBand->setColor( color );
181 mSelectionRect.setTopLeft( event->pos() );
184 mSelectionRect.setBottomRight( event->pos() );
185 if ( mSelectionRubberBand )
187 mSelectionRubberBand->setToCanvasRectangle( mSelectionRect );
188 mSelectionRubberBand->show();
193 mLastScenePos = scenePos;
198 if ( event->button() != Qt::LeftButton )
203 QPointF scenePos =
mCanvas->mapToScene( event->pos() );
204 const bool toggleSelection =
event->modifiers() & Qt::ShiftModifier;
206 if ( !toggleSelection && !mSelectedItems.empty() && mMouseHandles->sceneBoundingRect().contains( scenePos ) )
208 QGraphicsSceneMouseEvent forwardedEvent( QEvent::GraphicsSceneMousePress );
209 forwardedEvent.setPos( mMouseHandles->mapFromScene( scenePos ) );
210 forwardedEvent.setScenePos( scenePos );
211 forwardedEvent.setLastScenePos( mLastScenePos );
212 forwardedEvent.setButton( Qt::LeftButton );
213 mMouseHandles->mousePressEvent( &forwardedEvent );
217 mSelectionRect.setTopLeft( event->pos() );
218 mSelectionRect.setBottomRight( event->pos() );
221 mLastScenePos = scenePos;
226 if ( event->button() != Qt::LeftButton )
231 QPointF scenePos =
mCanvas->mapToScene( event->pos() );
233 if ( mMouseHandles->shouldBlockEvent( event ) )
235 QGraphicsSceneMouseEvent forwardedEvent( QEvent::GraphicsSceneMouseRelease );
236 forwardedEvent.setPos( mMouseHandles->mapFromScene( scenePos ) );
237 forwardedEvent.setScenePos( scenePos );
238 forwardedEvent.setLastScenePos( mLastScenePos );
239 forwardedEvent.setButton( Qt::LeftButton );
240 mMouseHandles->mouseReleaseEvent( &forwardedEvent );
254 mSelectionRubberBand.reset();
260 setSelectedItemsFromRect( searchRect, ( event->modifiers() & Qt::ShiftModifier ) );
264 setSelectedItemFromPoint( event->
mapPoint(), ( event->modifiers() & Qt::ShiftModifier ) );
267 mMouseHandles->setSelected( !mSelectedItems.empty() );
273 if ( mMouseHandles->isDragging() || mMouseHandles->isResizing() || mMouseHandles->isRotating() )
278 if ( mSelectedItems.empty() )
283 if ( event->key() == Qt::Key_C || event->key() == Qt::Key_X )
285 mCopiedItems.clear();
286 mCopiedItemsTopLeft =
toMapCoordinates( QPoint( mMouseHandles->sceneBoundingRect().topLeft().x(), mMouseHandles->sceneBoundingRect().topLeft().y() ) );
287 for ( std::unique_ptr<QgsAnnotationItemRubberBand> &selectedItem : mSelectedItems )
289 mCopiedItems << qMakePair( selectedItem->layerId(), selectedItem->itemId() );
291 if ( event->key() == Qt::Key_C )
297 else if ( event->key() == Qt::Key_V )
299 const QgsPointXY copiedItemsSceneTopLeft =
mCanvas->mapSettings().mapToPixel().transform( mCopiedItemsTopLeft );
300 const double deltaX = mLastScenePos.x() - copiedItemsSceneTopLeft.
x();
301 const double deltaY = mLastScenePos.y() - copiedItemsSceneTopLeft.
y();
302 if ( !mSelectedItems.empty() )
304 mSelectedItems.clear();
307 for (
const QPair<QString, QString> &copiedItem : mCopiedItems )
312 if ( !annotationLayer )
316 QString pastedItemId = annotationLayer->
addItem( annotationItem->clone() );
318 mSelectedItems.push_back( std::make_unique<QgsAnnotationItemRubberBand>( annotationLayer->
id(), pastedItemId,
mCanvas ) );
319 attemptMoveBy( mSelectedItems.back().get(), deltaX, deltaY );
323 updateSelectedItem();
328 if ( event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete || event->key() == Qt::Key_X )
330 while ( !mSelectedItems.empty() )
334 annotationLayer->removeItem( mSelectedItems.back()->itemId() );
336 mSelectedItems.pop_back();
339 updateSelectedItem();
342 else if ( event->key() == Qt::Key_Left
343 || event->key() == Qt::Key_Right
344 || event->key() == Qt::Key_Up
345 || event->key() == Qt::Key_Down )
347 const int pixels = (
event->modifiers() & Qt::ShiftModifier ) ? 1 : 50;
350 if ( event->key() == Qt::Key_Up )
354 else if ( event->key() == Qt::Key_Down )
358 else if ( event->key() == Qt::Key_Left )
362 else if ( event->key() == Qt::Key_Right )
367 for ( std::unique_ptr<QgsAnnotationItemRubberBand> &selectedItem : mSelectedItems )
377 QList<QgsAnnotationItemRubberBand *> items;
378 for (
const std::unique_ptr<QgsAnnotationItemRubberBand> &selectedItem : mSelectedItems )
380 items << selectedItem.get();
385void QgsMapToolSelectAnnotation::onCanvasRefreshed()
388 if ( !renderedItemResults )
393 const QList<QgsRenderedItemDetails *> items = renderedItemResults->
renderedItems();
394 bool needsSelectedItemsUpdate =
false;
395 for ( std::unique_ptr<QgsAnnotationItemRubberBand> &selectedItem : mSelectedItems )
397 if ( selectedItem->needsUpdatedBoundingBox() )
399 needsSelectedItemsUpdate =
true;
401 auto it = std::find_if( items.begin(), items.end(), [&selectedItem](
const QgsRenderedItemDetails *item ) {
402 if ( const QgsRenderedAnnotationItemDetails *annotationItem = dynamic_cast<const QgsRenderedAnnotationItemDetails *>( item ) )
404 if ( annotationItem->itemId() == selectedItem->itemId() && annotationItem->layerId() == selectedItem->layerId() )
412 if ( it != items.end() )
414 selectedItem->updateBoundingBox( ( *it )->boundingBox() );
419 if ( needsSelectedItemsUpdate )
421 emit selectedItemsChanged();
425long long QgsMapToolSelectAnnotation::annotationItemRubberBandIndexFromId(
const QString &layerId,
const QString &itemId )
427 if ( mSelectedItems.empty() )
432 auto it = std::find_if( mSelectedItems.begin(), mSelectedItems.end(), [&layerId, &itemId](
auto &item ) { return item->layerId() == layerId && item->itemId() == itemId; } );
433 return it != mSelectedItems.end() ? std::distance( mSelectedItems.begin(), it ) : -1;
436void QgsMapToolSelectAnnotation::setSelectedItemsFromRect(
const QgsRectangle &mapRect,
bool toggleSelection )
439 if ( !renderedItemResults )
441 if ( !toggleSelection )
443 clearSelectedItems();
451 if ( !toggleSelection )
453 clearSelectedItems();
458 if ( !toggleSelection )
460 mSelectedItems.clear();
462 for (
const QgsRenderedAnnotationItemDetails *item : items )
465 if ( annotationItem )
467 if ( toggleSelection )
469 long long index = annotationItemRubberBandIndexFromId( item->layerId(), item->itemId() );
472 mSelectedItems.erase( mSelectedItems.begin() + index );
476 mSelectedItems.push_back( std::make_unique<QgsAnnotationItemRubberBand>( item->layerId(), item->itemId(),
mCanvas ) );
477 mSelectedItems.back()->updateBoundingBox( item->boundingBox() );
481 updateSelectedItem();
484void QgsMapToolSelectAnnotation::setSelectedItemFromPoint(
const QgsPointXY &mapPoint,
bool toggleSelection )
486 QgsRectangle searchRect = QgsRectangle( mapPoint.
x(), mapPoint.
y(), mapPoint.
x(), mapPoint.
y() );
490 if ( !renderedItemResults )
492 clearSelectedItems();
499 if ( !toggleSelection )
501 clearSelectedItems();
506 QgsRectangle itemBounds;
507 const QgsRenderedAnnotationItemDetails *closestItem =
findClosestItemToPoint( mapPoint, items, itemBounds );
510 if ( !toggleSelection )
512 clearSelectedItems();
517 long long index = annotationItemRubberBandIndexFromId( closestItem->
layerId(), closestItem->
itemId() );
520 if ( toggleSelection )
522 mSelectedItems.erase( mSelectedItems.begin() + index );
527 if ( !toggleSelection )
529 mSelectedItems.clear();
532 mSelectedItems.push_back( std::make_unique<QgsAnnotationItemRubberBand>( closestItem->
layerId(), closestItem->
itemId(),
mCanvas ) );
533 mSelectedItems.back()->updateBoundingBox( closestItem->
boundingBox() );
536 updateSelectedItem();
539void QgsMapToolSelectAnnotation::updateSelectedItem()
541 if ( mSelectedItems.size() > 1 )
545 else if ( mSelectedItems.size() == 1 )
555void QgsMapToolSelectAnnotation::clearSelectedItems()
557 const bool hadSelection = !mSelectedItems.empty();
558 mSelectedItems.clear();
563 updateSelectedItem();
572 const double mupp =
mCanvas->mapSettings().mapUnitsPerPixel();
573 QgsVector translation( deltaX * mupp, -deltaY * mupp );
580 const QList<QgsAnnotationItemNode> itemNodes = annotationItem->
nodesV2( context );
583 QgsPointXY mapPoint =
mCanvas->mapSettings().layerToMapCoordinates( annotationLayer, node.point() );
584 mapPoint += translation;
585 QgsPointXY modifiedPoint =
mCanvas->mapSettings().mapToLayerCoordinates( annotationLayer, mapPoint );
588 switch ( annotationLayer->
applyEditV2( &operation, context ) )
600 boundingBox += translation;
615 switch ( annotationLayer->applyEditV2( &operation, context ) )
633 const double widthRatio = rect.width() / annotationItemRubberBand->
boundingRect().width();
634 const double heightRatio = rect.height() / annotationItemRubberBand->
boundingRect().height();
635 const double deltaX = rect.x() - annotationItemRubberBand->x() + 1;
636 const double deltaY = rect.y() - annotationItemRubberBand->y() + 1;
647 const QList<QgsAnnotationItemNode> itemNodes = annotationItem->
nodesV2( context );
650 const double modifiedX = modifiedBoundingBox.
xMinimum() + modifiedBoundingBox.
width() * ( ( node.point().x() - boundingBox.
xMinimum() ) / boundingBox.
width() );
651 const double modifiedY = modifiedBoundingBox.
yMaximum() - modifiedBoundingBox.
height() * ( ( boundingBox.
yMaximum() - node.point().y() ) / boundingBox.
height() );
652 QgsPointXY modifiedPoint( modifiedX, modifiedY );
654 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.