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 || event->key() == Qt::Key_Right || event->key() == Qt::Key_Up || event->key() == Qt::Key_Down )
301 const int pixels = (
event->modifiers() & Qt::ShiftModifier ) ? 1 : 50;
304 if ( event->key() == Qt::Key_Up )
308 else if ( event->key() == Qt::Key_Down )
312 else if ( event->key() == Qt::Key_Left )
316 else if ( event->key() == Qt::Key_Right )
321 for ( std::unique_ptr<QgsAnnotationItemRubberBand> &selectedItem : mSelectedItems )
331 if ( mMouseHandles->isDragging() || mMouseHandles->isResizing() || mMouseHandles->isRotating() )
336 QKeySequence keySequence( event->key() | event->modifiers() );
338 if ( keySequence == QKeySequence::Copy || keySequence == QKeySequence::Cut )
340 mCopiedItems.clear();
341 mCopiedItemsTopLeft =
toMapCoordinates( QPoint( mMouseHandles->sceneBoundingRect().topLeft().x(), mMouseHandles->sceneBoundingRect().topLeft().y() ) );
342 for ( std::unique_ptr<QgsAnnotationItemRubberBand> &selectedItem : mSelectedItems )
346 std::unique_ptr<QgsAnnotationItem> copiedItem;
347 copiedItem.reset( annotationItem->clone() );
348 mCopiedItems.push_back( std::move( copiedItem ) );
352 if ( keySequence == QKeySequence::Cut )
354 while ( !mSelectedItems.empty() )
358 annotationLayer->removeItem( mSelectedItems.back()->itemId() );
360 mSelectedItems.pop_back();
367 else if ( keySequence == QKeySequence::Paste )
369 const QgsPointXY copiedItemsSceneTopLeft =
mCanvas->mapSettings().mapToPixel().transform( mCopiedItemsTopLeft );
370 const double deltaX = mLastScenePos.x() - copiedItemsSceneTopLeft.
x();
371 const double deltaY = mLastScenePos.y() - copiedItemsSceneTopLeft.
y();
372 if ( !mSelectedItems.empty() )
374 mSelectedItems.clear();
377 for ( std::unique_ptr<QgsAnnotationItem> &copiedItem : mCopiedItems )
380 if ( !annotationLayer )
384 QString pastedItemId = annotationLayer->
addItem( copiedItem->clone() );
385 mSelectedItems.push_back( std::make_unique<QgsAnnotationItemRubberBand>( annotationLayer->
id(), pastedItemId,
mCanvas ) );
386 attemptMoveBy( mSelectedItems.back().get(), deltaX, deltaY );
387 mSelectedItems.back().get()->setNeedsUpdatedBoundingBox(
true );
399 QList<QgsAnnotationItemRubberBand *> items;
400 for (
const std::unique_ptr<QgsAnnotationItemRubberBand> &selectedItem : mSelectedItems )
402 if ( !selectedItem.get()->boundingBox().isEmpty() )
404 items << selectedItem.get();
410void QgsMapToolSelectAnnotation::onCanvasRefreshed()
413 if ( !renderedItemResults )
418 const QList<QgsRenderedItemDetails *> items = renderedItemResults->
renderedItems();
419 bool needsSelectedItemsUpdate =
false;
420 for ( std::unique_ptr<QgsAnnotationItemRubberBand> &selectedItem : mSelectedItems )
422 if ( selectedItem->needsUpdatedBoundingBox() )
424 needsSelectedItemsUpdate =
true;
426 auto it = std::find_if( items.begin(), items.end(), [&selectedItem](
const QgsRenderedItemDetails *item ) {
427 if ( const QgsRenderedAnnotationItemDetails *annotationItem = dynamic_cast<const QgsRenderedAnnotationItemDetails *>( item ) )
429 if ( annotationItem->itemId() == selectedItem->itemId() && annotationItem->layerId() == selectedItem->layerId() )
437 if ( it != items.end() )
439 selectedItem->updateBoundingBox( ( *it )->boundingBox() );
444 if ( needsSelectedItemsUpdate )
446 emit selectedItemsChanged();
450long long QgsMapToolSelectAnnotation::annotationItemRubberBandIndexFromId(
const QString &layerId,
const QString &itemId )
452 if ( mSelectedItems.empty() )
457 auto it = std::find_if( mSelectedItems.begin(), mSelectedItems.end(), [&layerId, &itemId](
auto &item ) { return item->layerId() == layerId && item->itemId() == itemId; } );
458 return it != mSelectedItems.end() ? std::distance( mSelectedItems.begin(), it ) : -1;
461void QgsMapToolSelectAnnotation::setSelectedItemsFromRect(
const QgsRectangle &mapRect,
bool toggleSelection )
464 if ( !renderedItemResults )
466 if ( !toggleSelection )
468 clearSelectedItems();
476 if ( !toggleSelection )
478 clearSelectedItems();
483 if ( !toggleSelection )
485 mSelectedItems.clear();
487 for (
const QgsRenderedAnnotationItemDetails *item : items )
490 if ( annotationItem )
492 if ( toggleSelection )
494 long long index = annotationItemRubberBandIndexFromId( item->layerId(), item->itemId() );
497 mSelectedItems.erase( mSelectedItems.begin() + index );
501 mSelectedItems.push_back( std::make_unique<QgsAnnotationItemRubberBand>( item->layerId(), item->itemId(),
mCanvas ) );
502 mSelectedItems.back()->updateBoundingBox( item->boundingBox() );
506 updateSelectedItem();
509void QgsMapToolSelectAnnotation::setSelectedItemFromPoint(
const QgsPointXY &mapPoint,
bool toggleSelection )
511 QgsRectangle searchRect = QgsRectangle( mapPoint.
x(), mapPoint.
y(), mapPoint.
x(), mapPoint.
y() );
515 if ( !renderedItemResults )
517 clearSelectedItems();
524 if ( !toggleSelection )
526 clearSelectedItems();
531 QgsRectangle itemBounds;
532 const QgsRenderedAnnotationItemDetails *closestItem =
findClosestItemToPoint( mapPoint, items, itemBounds );
535 if ( !toggleSelection )
537 clearSelectedItems();
542 long long index = annotationItemRubberBandIndexFromId( closestItem->
layerId(), closestItem->
itemId() );
545 if ( toggleSelection )
547 mSelectedItems.erase( mSelectedItems.begin() + index );
552 if ( !toggleSelection )
554 mSelectedItems.clear();
557 mSelectedItems.push_back( std::make_unique<QgsAnnotationItemRubberBand>( closestItem->
layerId(), closestItem->
itemId(),
mCanvas ) );
558 mSelectedItems.back()->updateBoundingBox( closestItem->
boundingBox() );
561 updateSelectedItem();
564void QgsMapToolSelectAnnotation::updateSelectedItem()
566 if ( mSelectedItems.size() > 1 )
570 else if ( mSelectedItems.size() == 1 )
580void QgsMapToolSelectAnnotation::clearSelectedItems()
582 const bool hadSelection = !mSelectedItems.empty();
583 mSelectedItems.clear();
588 updateSelectedItem();
597 const double mupp =
mCanvas->mapSettings().mapUnitsPerPixel();
598 QgsVector translation( deltaX * mupp, -deltaY * mupp );
605 const QList<QgsAnnotationItemNode> itemNodes = annotationItem->
nodesV2( context );
608 QgsPointXY mapPoint =
mCanvas->mapSettings().layerToMapCoordinates( annotationLayer, node.point() );
609 mapPoint += translation;
610 QgsPointXY modifiedPoint =
mCanvas->mapSettings().mapToLayerCoordinates( annotationLayer, mapPoint );
613 switch ( annotationLayer->
applyEditV2( &operation, context ) )
625 boundingBox += translation;
640 switch ( annotationLayer->applyEditV2( &operation, context ) )
658 const double widthRatio = rect.width() / annotationItemRubberBand->
boundingRect().width();
659 const double heightRatio = rect.height() / annotationItemRubberBand->
boundingRect().height();
660 const double deltaX = rect.x() - annotationItemRubberBand->x() + 1;
661 const double deltaY = rect.y() - annotationItemRubberBand->y() + 1;
673 const QList<QgsAnnotationItemNode> itemNodes = annotationItem->
nodesV2( context );
676 const double modifiedX = modifiedBoundingBox.
xMinimum() + modifiedBoundingBox.
width() * ( ( node.point().x() - boundingBox.
xMinimum() ) / boundingBox.
width() );
677 const double modifiedY = modifiedBoundingBox.
yMaximum() - modifiedBoundingBox.
height() * ( ( boundingBox.
yMaximum() - node.point().y() ) / boundingBox.
height() );
678 QgsPointXY modifiedPoint( modifiedX, modifiedY );
680 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.