[FEATURE][layouts] Add a dedicated toolbar action to create north arrows

This is a shortcut to adding a picture item, setting it to a north arrow
picture, and linking it with a map. The end result is identical, but it's
much easier for new users to understand if we expose it as an explicit
"North Arrow" item.

Even experienced users will likely appreciate the improved workflow,
including automatically linking the picture rotation to a sensible
default map choice (if a map is selected, it's used. If not, the
topmost map item under the newly drawn north arrow is used. If there's
none, the layout's 'reference map' (or biggest map) is used as a
fallback)

Fixes #30162
This commit is contained in:
Nyall Dawson 2019-06-12 14:41:11 +10:00 committed by Mathieu Pellerin
parent 26c83dac78
commit 0ccc8f15ff
2 changed files with 70 additions and 42 deletions

View File

@ -756,6 +756,7 @@
<file>themes/default/mActionNewVirtualLayer.svg</file>
<file>themes/default/mActionDoubleArrowRight.svg</file>
<file>themes/default/mActionDoubleArrowLeft.svg</file>
<file>north_arrows/layout_default_north_arrow.svg</file>
</qresource>
<qresource prefix="/images/tips">
<file alias="symbol_levels.png">qgis_tips/symbol_levels.png</file>

View File

@ -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<QgsLayoutItemMap *> 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<QgsLayoutItemMap *> 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<QgsLayoutItemMap *> 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<QgsLayoutItemMap *> 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 >(