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 >(