Skip to content

Commit

Permalink
[feature][layouts] Add menu entry to add dynamic text labels
Browse files Browse the repository at this point in the history
easily to a layout

The new "Add Item" -> "Dynamic Text" menu contains a bunch of preset
handy dynamic text expressions which users can use to insert a label
automatically containing the corresponding expression. E.g.

Add Item -> Dynamic Text -> Layout Name

will insert a label containing the expression [% @layout_name %].

This raises discoverability and user-friendliness of inserting
dynamic labels
  • Loading branch information
nyalldawson committed Jan 11, 2021
1 parent f81737c commit ad94415
Show file tree
Hide file tree
Showing 13 changed files with 355 additions and 29 deletions.
17 changes: 16 additions & 1 deletion python/gui/auto_generated/layout/qgslayoutitemguiregistry.sip.in
Expand Up @@ -188,6 +188,18 @@ QgsLayoutItemGuiRegistry is not usually directly created, but rather accessed th
%Docstring
Returns the metadata for the specified item ``metadataId``. Returns ``None`` if
a corresponding ``metadataId`` was not found in the registry.
%End

int metadataIdForItemType( int type ) const;
%Docstring
Returns the GUI item metadata ID which corresponds to the specified layout item ``type``.

In the case that multiple GUI metadata classes exist for a single layout item ``type`` then
only the first encountered GUI metadata ID will be returned.

Returns -1 if no matching metadata is found in the GUI registry.

.. versionadded:: 3.18
%End

bool addLayoutItemGuiMetadata( QgsLayoutItemAbstractGuiMetadata *metadata /Transfer/ );
Expand Down Expand Up @@ -219,12 +231,15 @@ Returns a reference to the item group with matching ``id``.
Creates a new instance of a layout item given the item metadata ``metadataId``, target ``layout``.
%End

void newItemAddedToLayout( int metadataId, QgsLayoutItem *item );
void newItemAddedToLayout( int metadataId, QgsLayoutItem *item, const QVariantMap &properties = QVariantMap() );
%Docstring
Called when a newly created item of the associated metadata ``metadataId`` has been added to a layout.

This is only called for additions which result from GUI operations - i.e. it is not
called for items added from templates.

Since QGIS 3.18 the optional ``properties`` argument can be used to pass custom properties to the
:py:func:`QgsLayoutItemGuiMetadata.newItemAddedToLayout()` function.
%End


Expand Down
23 changes: 23 additions & 0 deletions python/gui/auto_generated/layout/qgslayoutviewtooladditem.sip.in
Expand Up @@ -49,9 +49,32 @@ from :py:class:`QgsLayoutItemGuiRegistry`.

virtual void layoutReleaseEvent( QgsLayoutViewMouseEvent *event );

virtual void activate();

virtual void deactivate();


QVariantMap customProperties() const;
%Docstring
Returns any custom properties set for the tool.

.. seealso:: :py:func:`setCustomProperties`

.. versionadded:: 3.18
%End

void setCustomProperties( const QVariantMap &properties );
%Docstring
Sets custom ``properties`` for the tool.

These properties are transient, and are cleared whenever the tool is activated. Callers must ensure
that the properties are set only after the tool is activated.

.. seealso:: :py:func:`customProperties`

.. versionadded:: 3.18
%End

signals:

void createdItem();
Expand Down
18 changes: 18 additions & 0 deletions src/app/layout/qgslayoutdesignerdialog.cpp
Expand Up @@ -73,6 +73,7 @@
#include "qgsabstractvaliditycheck.h"
#include "qgsvaliditycheckcontext.h"
#include "qgsprojectviewsettings.h"
#include "qgslayoutlabelwidget.h"
#include "ui_defaults.h"

#include <QShortcut>
Expand Down Expand Up @@ -407,6 +408,23 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla

connect( mActionClose, &QAction::triggered, this, &QWidget::close );

mDynamicTextMenu = new QMenu( tr( "Dynamic Text" ), this );

connect( mDynamicTextMenu, &QMenu::aboutToShow, this, [ = ]
{
mDynamicTextMenu->clear();
// we need to rebuild this on each show, as the content varies depending on other available items...
QgsLayoutLabelWidget::buildInsertDynamicTextMenu( mLayout, mDynamicTextMenu, [ = ]( const QString & expression )
{
activateNewItemCreationTool( QgsGui::layoutItemGuiRegistry()->metadataIdForItemType( QgsLayoutItemRegistry::LayoutLabel ), false );
QVariantMap properties;
properties.insert( QStringLiteral( "expression" ), expression );
mAddItemTool->setCustomProperties( properties );
} );
} );

mItemMenu->addMenu( mDynamicTextMenu );

// populate with initial items...
const QList< int > itemMetadataIds = QgsGui::layoutItemGuiRegistry()->itemMetadataIds();
for ( int id : itemMetadataIds )
Expand Down
2 changes: 2 additions & 0 deletions src/app/layout/qgslayoutdesignerdialog.h
Expand Up @@ -465,6 +465,8 @@ class QgsLayoutDesignerDialog: public QMainWindow, public Ui::QgsLayoutDesignerB
QAction *mActionPaste = nullptr;
QProgressBar *mStatusProgressBar = nullptr;

QMenu *mDynamicTextMenu = nullptr;

struct PanelStatus
{
PanelStatus( bool visible = true, bool active = false )
Expand Down
12 changes: 6 additions & 6 deletions src/gui/layout/qgslayoutguiutils.cpp
Expand Up @@ -117,7 +117,7 @@ void QgsLayoutGuiUtils::registerGuiForKnownItemTypes( QgsMapCanvas *mapCanvas )
{
return new QgsLayoutMapWidget( qobject_cast< QgsLayoutItemMap * >( item ), mapCanvas );
}, createRubberBand );
mapItemMetadata->setItemAddedToLayoutFunction( [ = ]( QgsLayoutItem * item )
mapItemMetadata->setItemAddedToLayoutFunction( [ = ]( QgsLayoutItem * item, const QVariantMap & )
{
QgsLayoutItemMap *map = qobject_cast< QgsLayoutItemMap * >( item );
Q_ASSERT( map );
Expand Down Expand Up @@ -176,12 +176,12 @@ void QgsLayoutGuiUtils::registerGuiForKnownItemTypes( QgsMapCanvas *mapCanvas )
{
return new QgsLayoutLabelWidget( qobject_cast< QgsLayoutItemLabel * >( item ) );
}, createRubberBand );
labelItemMetadata->setItemAddedToLayoutFunction( [ = ]( QgsLayoutItem * item )
labelItemMetadata->setItemAddedToLayoutFunction( [ = ]( QgsLayoutItem * item, const QVariantMap & properties )
{
QgsLayoutItemLabel *label = qobject_cast< QgsLayoutItemLabel * >( item );
Q_ASSERT( label );

label->setText( QObject::tr( "Lorem ipsum" ) );
label->setText( properties.value( QStringLiteral( "expression" ) ).toString().isEmpty() ? QObject::tr( "Lorem ipsum" ) : QStringLiteral( "[% %1 %]" ).arg( properties.value( QStringLiteral( "expression" ) ).toString() ) );
if ( QApplication::isRightToLeft() )
{
label->setHAlign( Qt::AlignRight );
Expand All @@ -205,7 +205,7 @@ void QgsLayoutGuiUtils::registerGuiForKnownItemTypes( QgsMapCanvas *mapCanvas )
{
return new QgsLayoutLegendWidget( qobject_cast< QgsLayoutItemLegend * >( item ), mapCanvas );
}, createRubberBand );
legendItemMetadata->setItemAddedToLayoutFunction( [ = ]( QgsLayoutItem * item )
legendItemMetadata->setItemAddedToLayoutFunction( [ = ]( QgsLayoutItem * item, const QVariantMap & )
{
QgsLayoutItemLegend *legend = qobject_cast< QgsLayoutItemLegend * >( item );
Q_ASSERT( legend );
Expand Down Expand Up @@ -246,7 +246,7 @@ void QgsLayoutGuiUtils::registerGuiForKnownItemTypes( QgsMapCanvas *mapCanvas )
{
return new QgsLayoutScaleBarWidget( qobject_cast< QgsLayoutItemScaleBar * >( item ) );
}, createRubberBand );
scalebarItemMetadata->setItemAddedToLayoutFunction( [ = ]( QgsLayoutItem * item )
scalebarItemMetadata->setItemAddedToLayoutFunction( [ = ]( QgsLayoutItem * item, const QVariantMap & )
{
QgsLayoutItemScaleBar *scalebar = qobject_cast< QgsLayoutItemScaleBar * >( item );
Q_ASSERT( scalebar );
Expand Down Expand Up @@ -294,7 +294,7 @@ void QgsLayoutGuiUtils::registerGuiForKnownItemTypes( QgsMapCanvas *mapCanvas )
picture->setId( northArrowCount > 0 ? QObject::tr( "North Arrow %1" ).arg( northArrowCount + 1 ) : QObject::tr( "North Arrow" ) );
return picture.release();
} );
northArrowMetadata->setItemAddedToLayoutFunction( [ = ]( QgsLayoutItem * item )
northArrowMetadata->setItemAddedToLayoutFunction( [ = ]( QgsLayoutItem * item, const QVariantMap & )
{
QgsLayoutItemPicture *picture = qobject_cast< QgsLayoutItemPicture * >( item );
Q_ASSERT( picture );
Expand Down
29 changes: 26 additions & 3 deletions src/gui/layout/qgslayoutitemguiregistry.cpp
Expand Up @@ -57,6 +57,16 @@ QgsLayoutItemAbstractGuiMetadata *QgsLayoutItemGuiRegistry::itemMetadata( int me
return mMetadata.value( metadataId );
}

int QgsLayoutItemGuiRegistry::metadataIdForItemType( int type ) const
{
for ( auto it = mMetadata.constBegin(); it != mMetadata.constEnd(); ++it )
{
if ( it.value()->type() == type )
return it.key();
}
return -1;
}

bool QgsLayoutItemGuiRegistry::addLayoutItemGuiMetadata( QgsLayoutItemAbstractGuiMetadata *metadata )
{
if ( !metadata )
Expand Down Expand Up @@ -95,12 +105,19 @@ QgsLayoutItem *QgsLayoutItemGuiRegistry::createItem( int metadataId, QgsLayout *
return QgsApplication::layoutItemRegistry()->createItem( type, layout );
}

void QgsLayoutItemGuiRegistry::newItemAddedToLayout( int metadataId, QgsLayoutItem *item )
void QgsLayoutItemGuiRegistry::newItemAddedToLayout( int metadataId, QgsLayoutItem *item, const QVariantMap &properties )
{
if ( !mMetadata.contains( metadataId ) )
return;

mMetadata.value( metadataId )->newItemAddedToLayout( item );
if ( QgsLayoutItemGuiMetadata *metadata = dynamic_cast<QgsLayoutItemGuiMetadata *>( mMetadata.value( metadataId ) ) )
{
metadata->newItemAddedToLayout( item, properties );
}
else
{
mMetadata.value( metadataId )->newItemAddedToLayout( item );
}
}

QgsLayoutItemBaseWidget *QgsLayoutItemGuiRegistry::createItemWidget( QgsLayoutItem *item ) const
Expand Down Expand Up @@ -153,5 +170,11 @@ QgsLayoutItem *QgsLayoutItemGuiMetadata::createItem( QgsLayout *layout )
void QgsLayoutItemGuiMetadata::newItemAddedToLayout( QgsLayoutItem *item )
{
if ( mAddedToLayoutFunc )
mAddedToLayoutFunc( item );
mAddedToLayoutFunc( item, QVariantMap() );
}

void QgsLayoutItemGuiMetadata::newItemAddedToLayout( QgsLayoutItem *item, const QVariantMap &properties )
{
if ( mAddedToLayoutFunc )
mAddedToLayoutFunc( item, properties );
}
21 changes: 19 additions & 2 deletions src/gui/layout/qgslayoutitemguiregistry.h
Expand Up @@ -170,7 +170,7 @@ typedef std::function<QgsLayoutViewRubberBand *( QgsLayoutView * )> QgsLayoutIte
typedef std::function<QAbstractGraphicsShapeItem *( QgsLayoutView * )> QgsLayoutNodeItemRubberBandFunc SIP_SKIP;

//! Layout item added to layout callback
typedef std::function<void ( QgsLayoutItem * )> QgsLayoutItemAddedToLayoutFunc SIP_SKIP;
typedef std::function<void ( QgsLayoutItem *, const QVariantMap & )> QgsLayoutItemAddedToLayoutFunc SIP_SKIP;

#ifndef SIP_RUN

Expand Down Expand Up @@ -272,8 +272,10 @@ class GUI_EXPORT QgsLayoutItemGuiMetadata : public QgsLayoutItemAbstractGuiMetad
QgsLayoutItemBaseWidget *createItemWidget( QgsLayoutItem *item ) override { return mWidgetFunc ? mWidgetFunc( item ) : nullptr; }
QgsLayoutViewRubberBand *createRubberBand( QgsLayoutView *view ) override { return mRubberBandFunc ? mRubberBandFunc( view ) : nullptr; }
QAbstractGraphicsShapeItem *createNodeRubberBand( QgsLayoutView *view ) override { return mNodeRubberBandFunc ? mNodeRubberBandFunc( view ) : nullptr; }

QgsLayoutItem *createItem( QgsLayout *layout ) override;
void newItemAddedToLayout( QgsLayoutItem *item ) override;
void newItemAddedToLayout( QgsLayoutItem *item, const QVariantMap &properties );

protected:
QIcon mIcon;
Expand Down Expand Up @@ -370,6 +372,18 @@ class GUI_EXPORT QgsLayoutItemGuiRegistry : public QObject
*/
QgsLayoutItemAbstractGuiMetadata *itemMetadata( int metadataId ) const;

/**
* Returns the GUI item metadata ID which corresponds to the specified layout item \a type.
*
* In the case that multiple GUI metadata classes exist for a single layout item \a type then
* only the first encountered GUI metadata ID will be returned.
*
* Returns -1 if no matching metadata is found in the GUI registry.
*
* \since QGIS 3.18
*/
int metadataIdForItemType( int type ) const;

/**
* Registers the gui metadata for a new layout item type. Takes ownership of the metadata instance.
*/
Expand Down Expand Up @@ -417,8 +431,11 @@ class GUI_EXPORT QgsLayoutItemGuiRegistry : public QObject
*
* This is only called for additions which result from GUI operations - i.e. it is not
* called for items added from templates.
*
* Since QGIS 3.18 the optional \a properties argument can be used to pass custom properties to the
* QgsLayoutItemGuiMetadata::newItemAddedToLayout() function.
*/
void newItemAddedToLayout( int metadataId, QgsLayoutItem *item );
void newItemAddedToLayout( int metadataId, QgsLayoutItem *item, const QVariantMap &properties = QVariantMap() );

/*
* IMPORTANT: While it seems like /Factory/ would be the correct annotations here, that's not
Expand Down

0 comments on commit ad94415

Please sign in to comment.