diff --git a/images/images.qrc b/images/images.qrc
index 5acee5dee5e..7b353f88d33 100644
--- a/images/images.qrc
+++ b/images/images.qrc
@@ -756,6 +756,7 @@
themes/default/mActionNewVirtualLayer.svg
themes/default/mActionDoubleArrowRight.svg
themes/default/mActionDoubleArrowLeft.svg
+ north_arrows/layout_default_north_arrow.svg
qgis_tips/symbol_levels.png
diff --git a/src/app/layout/qgslayoutapputils.cpp b/src/app/layout/qgslayoutapputils.cpp
index f43e4497b57..d7f786975b9 100644
--- a/src/app/layout/qgslayoutapputils.cpp
+++ b/src/app/layout/qgslayoutapputils.cpp
@@ -47,6 +47,45 @@
#include "qgslayout3dmapwidget.h"
#endif
+/**
+ * Attempts to find the best guess at a map item to link \a referenceItem to,
+ * by:
+ * 1. Prioritising a selected map
+ * 2. If no selection, prioritising the topmost map the item was drawn over
+ * 3. If still none, use the layout's reference map (or biggest map)
+ */
+QgsLayoutItemMap *findSensibleDefaultLinkedMapItem( QgsLayoutItem *referenceItem )
+{
+ // start by trying to find a selected map
+ QList mapItems;
+ referenceItem->layout()->layoutItems( mapItems );
+
+ QgsLayoutItemMap *targetMap = nullptr;
+ for ( QgsLayoutItemMap *map : qgis::as_const( mapItems ) )
+ {
+ if ( map->isSelected() )
+ {
+ return map;
+ }
+ }
+
+ // nope, no selection... hm, was the item drawn over a map? If so, use the topmost intersecting one
+ double largestZValue = std::numeric_limits< double >::lowest();
+ for ( QgsLayoutItemMap *map : qgis::as_const( mapItems ) )
+ {
+ if ( map->collidesWithItem( referenceItem ) && map->zValue() > largestZValue )
+ {
+ targetMap = map;
+ largestZValue = map->zValue();
+ }
+ }
+ if ( targetMap )
+ return targetMap;
+
+ // ah frick it, just use the reference (or biggest!) map
+ return referenceItem->layout()->referenceMap();
+}
+
void QgsLayoutAppUtils::registerGuiForKnownItemTypes()
{
QgsLayoutItemGuiRegistry *registry = QgsGui::layoutItemGuiRegistry();
@@ -153,29 +192,8 @@ void QgsLayoutAppUtils::registerGuiForKnownItemTypes()
QgsLayoutItemLegend *legend = qobject_cast< QgsLayoutItemLegend * >( item );
Q_ASSERT( legend );
- QList mapItems;
- legend->layout()->layoutItems( mapItems );
-
// try to find a good map to link the legend with by default
- // start by trying to find a selected map
- QgsLayoutItemMap *targetMap = nullptr;
- for ( QgsLayoutItemMap *map : qgis::as_const( mapItems ) )
- {
- if ( map->isSelected() )
- {
- targetMap = map;
- break;
- }
- }
- // otherwise just use first map
- if ( !targetMap && !mapItems.isEmpty() )
- {
- targetMap = mapItems.at( 0 );
- }
- if ( targetMap )
- {
- legend->setLinkedMap( targetMap );
- }
+ legend->setLinkedMap( findSensibleDefaultLinkedMapItem( legend ) );
legend->updateLegend();
} );
@@ -194,26 +212,8 @@ void QgsLayoutAppUtils::registerGuiForKnownItemTypes()
QgsLayoutItemScaleBar *scalebar = qobject_cast< QgsLayoutItemScaleBar * >( item );
Q_ASSERT( scalebar );
- QList mapItems;
- scalebar->layout()->layoutItems( mapItems );
-
// try to find a good map to link the scalebar with by default
- // start by trying to find a selected map
- QgsLayoutItemMap *targetMap = nullptr;
- for ( QgsLayoutItemMap *map : qgis::as_const( mapItems ) )
- {
- if ( map->isSelected() )
- {
- targetMap = map;
- break;
- }
- }
- // otherwise just use first map
- if ( !targetMap && !mapItems.isEmpty() )
- {
- targetMap = mapItems.at( 0 );
- }
- if ( targetMap )
+ if ( QgsLayoutItemMap *targetMap = findSensibleDefaultLinkedMapItem( scalebar ) )
{
scalebar->setLinkedMap( targetMap );
scalebar->applyDefaultSize( scalebar->guessUnits() );
@@ -222,6 +222,34 @@ void QgsLayoutAppUtils::registerGuiForKnownItemTypes()
registry->addLayoutItemGuiMetadata( scalebarItemMetadata.release() );
+
+ // north arrow
+ std::unique_ptr< QgsLayoutItemGuiMetadata > northArrowMetadata = qgis::make_unique< QgsLayoutItemGuiMetadata>(
+ QgsLayoutItemRegistry::LayoutPicture, QObject::tr( "North Arrow" ), QgsApplication::getThemeIcon( QStringLiteral( "/north_arrow.svg" ) ),
+ [ = ]( QgsLayoutItem * item )->QgsLayoutItemBaseWidget *
+ {
+ return new QgsLayoutPictureWidget( qobject_cast< QgsLayoutItemPicture * >( item ) );
+ }, createRubberBand );
+ northArrowMetadata->setItemCreationFunction( []( QgsLayout * layout )->QgsLayoutItem *
+ {
+ std::unique_ptr< QgsLayoutItemPicture > picture = qgis::make_unique< QgsLayoutItemPicture >( layout );
+ picture->setNorthMode( QgsLayoutItemPicture::GridNorth );
+ picture->setPicturePath( QStringLiteral( ":/images/north_arrows/layout_default_north_arrow.svg" ) );
+ return picture.release();
+ } );
+ northArrowMetadata->setItemAddedToLayoutFunction( [ = ]( QgsLayoutItem * item )
+ {
+ QgsLayoutItemPicture *picture = qobject_cast< QgsLayoutItemPicture * >( item );
+ Q_ASSERT( picture );
+
+ QList mapItems;
+ picture->layout()->layoutItems( mapItems );
+
+ // try to find a good map to link the north arrow with by default
+ picture->setLinkedMap( findSensibleDefaultLinkedMapItem( picture ) );
+ } );
+ registry->addLayoutItemGuiMetadata( northArrowMetadata.release() );
+
// shape items
auto createShapeWidget =
@@ -271,7 +299,6 @@ void QgsLayoutAppUtils::registerGuiForKnownItemTypes()
} );
registry->addLayoutItemGuiMetadata( arrowMetadata.release() );
-
// node items
std::unique_ptr< QgsLayoutItemGuiMetadata > polygonMetadata = qgis::make_unique< QgsLayoutItemGuiMetadata >(