Skip to content

Commit

Permalink
[FEATURE][layouts] Add a dedicated toolbar action to create north arrows
Browse files Browse the repository at this point in the history
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
  • Loading branch information
nyalldawson authored and nirvn committed Jun 14, 2019
1 parent 26c83da commit 0ccc8f1
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 42 deletions.
1 change: 1 addition & 0 deletions images/images.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
111 changes: 69 additions & 42 deletions src/app/layout/qgslayoutapputils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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();
} );
Expand All @@ -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() );
Expand All @@ -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 =
Expand Down Expand Up @@ -271,7 +299,6 @@ void QgsLayoutAppUtils::registerGuiForKnownItemTypes()
} );
registry->addLayoutItemGuiMetadata( arrowMetadata.release() );


// node items

std::unique_ptr< QgsLayoutItemGuiMetadata > polygonMetadata = qgis::make_unique< QgsLayoutItemGuiMetadata >(
Expand Down

0 comments on commit 0ccc8f1

Please sign in to comment.