diff --git a/python/gui/auto_generated/layout/qgslayoutitemguiregistry.sip.in b/python/gui/auto_generated/layout/qgslayoutitemguiregistry.sip.in index 2ee14ccb48af..e5bdef476c00 100644 --- a/python/gui/auto_generated/layout/qgslayoutitemguiregistry.sip.in +++ b/python/gui/auto_generated/layout/qgslayoutitemguiregistry.sip.in @@ -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/ ); @@ -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 diff --git a/python/gui/auto_generated/layout/qgslayoutviewtooladditem.sip.in b/python/gui/auto_generated/layout/qgslayoutviewtooladditem.sip.in index 33c2b9c90b75..7a886a40a48d 100644 --- a/python/gui/auto_generated/layout/qgslayoutviewtooladditem.sip.in +++ b/python/gui/auto_generated/layout/qgslayoutviewtooladditem.sip.in @@ -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(); diff --git a/src/app/layout/qgslayoutdesignerdialog.cpp b/src/app/layout/qgslayoutdesignerdialog.cpp index 2c5480cf87a4..1ea99142430c 100644 --- a/src/app/layout/qgslayoutdesignerdialog.cpp +++ b/src/app/layout/qgslayoutdesignerdialog.cpp @@ -73,6 +73,7 @@ #include "qgsabstractvaliditycheck.h" #include "qgsvaliditycheckcontext.h" #include "qgsprojectviewsettings.h" +#include "qgslayoutlabelwidget.h" #include "ui_defaults.h" #include @@ -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 ) diff --git a/src/app/layout/qgslayoutdesignerdialog.h b/src/app/layout/qgslayoutdesignerdialog.h index 9b0e43bd1d6f..95575e52ee83 100644 --- a/src/app/layout/qgslayoutdesignerdialog.h +++ b/src/app/layout/qgslayoutdesignerdialog.h @@ -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 ) diff --git a/src/gui/layout/qgslayoutguiutils.cpp b/src/gui/layout/qgslayoutguiutils.cpp index 92085a181ffa..51f643043fdc 100644 --- a/src/gui/layout/qgslayoutguiutils.cpp +++ b/src/gui/layout/qgslayoutguiutils.cpp @@ -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 ); @@ -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 ); @@ -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 ); @@ -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 ); @@ -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 ); diff --git a/src/gui/layout/qgslayoutitemguiregistry.cpp b/src/gui/layout/qgslayoutitemguiregistry.cpp index 31a58be1a137..cad43e1e3d0e 100644 --- a/src/gui/layout/qgslayoutitemguiregistry.cpp +++ b/src/gui/layout/qgslayoutitemguiregistry.cpp @@ -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 ) @@ -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( mMetadata.value( metadataId ) ) ) + { + metadata->newItemAddedToLayout( item, properties ); + } + else + { + mMetadata.value( metadataId )->newItemAddedToLayout( item ); + } } QgsLayoutItemBaseWidget *QgsLayoutItemGuiRegistry::createItemWidget( QgsLayoutItem *item ) const @@ -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 ); } diff --git a/src/gui/layout/qgslayoutitemguiregistry.h b/src/gui/layout/qgslayoutitemguiregistry.h index 66bab7d69f88..6819b7eb6047 100644 --- a/src/gui/layout/qgslayoutitemguiregistry.h +++ b/src/gui/layout/qgslayoutitemguiregistry.h @@ -170,7 +170,7 @@ typedef std::function QgsLayoutIte typedef std::function QgsLayoutNodeItemRubberBandFunc SIP_SKIP; //! Layout item added to layout callback -typedef std::function QgsLayoutItemAddedToLayoutFunc SIP_SKIP; +typedef std::function QgsLayoutItemAddedToLayoutFunc SIP_SKIP; #ifndef SIP_RUN @@ -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; @@ -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. */ @@ -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 diff --git a/src/gui/layout/qgslayoutlabelwidget.cpp b/src/gui/layout/qgslayoutlabelwidget.cpp index 1c44a2cc8884..f9ae1e7694d1 100644 --- a/src/gui/layout/qgslayoutlabelwidget.cpp +++ b/src/gui/layout/qgslayoutlabelwidget.cpp @@ -20,10 +20,14 @@ #include "qgslayout.h" #include "qgsexpressionbuilderdialog.h" #include "qgsguiutils.h" +#include "qgslayoutitemmap.h" +#include "qgsvectorlayer.h" #include #include #include +#include +#include QgsLayoutLabelWidget::QgsLayoutLabelWidget( QgsLayoutItemLabel *label ) : QgsLayoutItemBaseWidget( nullptr, label ) @@ -64,6 +68,22 @@ QgsLayoutLabelWidget::QgsLayoutLabelWidget( QgsLayoutItemLabel *label ) connect( mFontButton, &QgsFontButton::changed, this, &QgsLayoutLabelWidget::fontChanged ); connect( mJustifyRadioButton, &QRadioButton::clicked, this, &QgsLayoutLabelWidget::justifyClicked ); + + mDynamicTextMenu = new QMenu( this ); + mDynamicTextButton->setMenu( mDynamicTextMenu ); + + connect( mDynamicTextMenu, &QMenu::aboutToShow, this, [ = ] + { + mDynamicTextMenu->clear(); + // we need to rebuild this on each show, as the content varies depending on other available items... + buildInsertDynamicTextMenu( mLabel->layout(), mDynamicTextMenu, [ = ]( const QString & expression ) + { + mLabel->beginCommand( tr( "Insert dynamic text" ) ); + mTextEdit->insertPlainText( "[%" + expression + "%]" ); + mLabel->endCommand(); + } ); + } ); + } void QgsLayoutLabelWidget::setMasterLayout( QgsMasterLayoutInterface *masterLayout ) @@ -72,6 +92,132 @@ void QgsLayoutLabelWidget::setMasterLayout( QgsMasterLayoutInterface *masterLayo mItemPropertiesWidget->setMasterLayout( masterLayout ); } +void QgsLayoutLabelWidget::buildInsertDynamicTextMenu( QgsLayout *layout, QMenu *menu, const std::function &callback ) +{ + auto addExpression = [&callback]( QMenu * menu, const QString & name, const QString & expression ) + { + QAction *action = new QAction( name, menu ); + connect( action, &QAction::triggered, action, [callback, expression] + { + callback( expression ); + } ); + menu->addAction( action ); + }; + + QMenu *dateMenu = new QMenu( tr( "Current Date" ), menu ); + for ( const std::pair< QString, QString > &expression : + { + std::make_pair( tr( "ISO Format (%1)" ).arg( QDateTime::currentDateTime().toString( QStringLiteral( "yyyy-MM-dd" ) ) ), QStringLiteral( "format_date(now(), 'yyyy-MM-dd')" ) ), + std::make_pair( tr( "Day/Month/Year (%1)" ).arg( QDateTime::currentDateTime().toString( QStringLiteral( "dd/MM/yyyy" ) ) ), QStringLiteral( "format_date(now(), 'dd/MM/yyyy')" ) ), + std::make_pair( tr( "Month/Day/Year (%1)" ).arg( QDateTime::currentDateTime().toString( QStringLiteral( "MM/dd/yyyy" ) ) ), QStringLiteral( "format_date(now(), 'MM/dd/yyyy')" ) ), + } ) + { + addExpression( dateMenu, expression.first, expression.second ); + } + menu->addMenu( dateMenu ); + + QMenu *mapsMenu = new QMenu( tr( "Map Properties" ), menu ); + QList< QgsLayoutItemMap * > maps; + layout->layoutItems( maps ); + for ( QgsLayoutItemMap *map : qgis::as_const( maps ) ) + { + // these expressions require the map to have a non-empty ID set + if ( map->id().isEmpty() ) + continue; + + QMenu *mapMenu = new QMenu( map->displayName(), mapsMenu ); + for ( const std::pair< QString, QString > &expression : + { + std::make_pair( tr( "Scale (%1)" ).arg( map->scale() ), QStringLiteral( "item_variables('%1')['map_scale']" ).arg( map->id() ) ), + std::make_pair( tr( "Rotation (%1)" ).arg( map->rotation() ), QStringLiteral( "item_variables('%1')['map_rotation']" ).arg( map->id() ) ), + } ) + { + addExpression( mapMenu, expression.first, expression.second ); + } + mapMenu->addSeparator(); + for ( const std::pair< QString, QString > &expression : + { + std::make_pair( tr( "CRS Identifier (%1)" ).arg( map->crs().authid() ), QStringLiteral( "item_variables('%1')['map_crs']" ).arg( map->id() ) ), + std::make_pair( tr( "CRS Name (%1)" ).arg( map->crs().description() ), QStringLiteral( "item_variables('%1')['map_crs_description']" ).arg( map->id() ) ), + std::make_pair( tr( "Ellipsoid Name (%1)" ).arg( map->crs().ellipsoidAcronym() ), QStringLiteral( "item_variables('%1')['map_crs_ellipsoid']" ).arg( map->id() ) ), + std::make_pair( tr( "Units (%1)" ).arg( QgsUnitTypes::toString( map->crs().mapUnits() ) ), QStringLiteral( "item_variables('%1')['map_units']" ).arg( map->id() ) ), + } ) + { + addExpression( mapMenu, expression.first, expression.second ); + } + mapMenu->addSeparator(); + + const QgsRectangle mapExtent = map->extent(); + const QgsPointXY center = mapExtent.center(); + for ( const std::pair< QString, QString > &expression : + { + std::make_pair( tr( "Center (X) (%1)" ).arg( center.x() ), QStringLiteral( "x(item_variables('%1')['map_extent_center'])" ).arg( map->id() ) ), + std::make_pair( tr( "Center (Y) (%1)" ).arg( center.y() ), QStringLiteral( "y(item_variables('%1')['map_extent_center'])" ).arg( map->id() ) ), + std::make_pair( tr( "X Minimum (%1)" ).arg( mapExtent.xMinimum() ), QStringLiteral( "x_min(item_variables('%1')['map_extent'])" ).arg( map->id() ) ), + std::make_pair( tr( "Y Minimum (%1)" ).arg( mapExtent.yMinimum() ), QStringLiteral( "y_min(item_variables('%1')['map_extent'])" ).arg( map->id() ) ), + std::make_pair( tr( "X Maximum (%1)" ).arg( mapExtent.xMaximum() ), QStringLiteral( "x_max(item_variables('%1')['map_extent'])" ).arg( map->id() ) ), + std::make_pair( tr( "Y Maximum (%1)" ).arg( mapExtent.yMaximum() ), QStringLiteral( "y_max(item_variables('%1')['map_extent'])" ).arg( map->id() ) ), + } ) + { + addExpression( mapMenu, expression.first, expression.second ); + } + mapMenu->addSeparator(); + for ( const std::pair< QString, QString > &expression : + { + std::make_pair( tr( "Layer Credits" ), QStringLiteral( "array_to_string(map_credits('%1'))" ).arg( map->id() ) ), + } ) + { + addExpression( mapMenu, expression.first, expression.second ); + } + mapsMenu->addMenu( mapMenu ); + } + menu->addMenu( mapsMenu ); + menu->addSeparator(); + + if ( layout->reportContext().layer() ) + { + const QgsFields fields = layout->reportContext().layer()->fields(); + + QMenu *fieldsMenu = new QMenu( tr( "Field" ), menu ); + for ( const QgsField &field : fields ) + { + addExpression( fieldsMenu, field.displayName(), QStringLiteral( "\"%1\"" ).arg( field.name() ) ); + } + + menu->addMenu( fieldsMenu ); + menu->addSeparator(); + } + + for ( const std::pair< QString, QString > &expression : + { + std::make_pair( tr( "Layout Name" ), QStringLiteral( "@layout_name" ) ), + std::make_pair( tr( "Layout Page Number" ), QStringLiteral( "@layout_page" ) ), + std::make_pair( tr( "Layout Page Count" ), QStringLiteral( "@layout_numpages" ) ) + } ) + { + addExpression( menu, expression.first, expression.second ); + } + menu->addSeparator(); + for ( const std::pair< QString, QString > &expression : + { + std::make_pair( tr( "Project Author" ), QStringLiteral( "@project_author" ) ), + std::make_pair( tr( "Project Title" ), QStringLiteral( "@project_title" ) ), + std::make_pair( tr( "Project Path" ), QStringLiteral( "@project_path" ) ) + } ) + { + addExpression( menu, expression.first, expression.second ); + } + menu->addSeparator(); + for ( const std::pair< QString, QString > &expression : + { + std::make_pair( tr( "Current User Name" ), QStringLiteral( "@user_full_name" ) ), + std::make_pair( tr( "Current User Account" ), QStringLiteral( "@user_account_name" ) ) + } ) + { + addExpression( menu, expression.first, expression.second ); + } +} + bool QgsLayoutLabelWidget::setNewItem( QgsLayoutItem *item ) { if ( item->type() != QgsLayoutItemRegistry::LayoutLabel ) diff --git a/src/gui/layout/qgslayoutlabelwidget.h b/src/gui/layout/qgslayoutlabelwidget.h index ba79b94d8bc5..f07dec934b2b 100644 --- a/src/gui/layout/qgslayoutlabelwidget.h +++ b/src/gui/layout/qgslayoutlabelwidget.h @@ -25,6 +25,7 @@ #include "ui_qgslayoutlabelwidgetbase.h" #include "qgslayoutitemwidget.h" #include "qgslayoutitemlabel.h" +#include /** * \ingroup gui @@ -41,6 +42,22 @@ class GUI_EXPORT QgsLayoutLabelWidget: public QgsLayoutItemBaseWidget, private U explicit QgsLayoutLabelWidget( QgsLayoutItemLabel *label ); void setMasterLayout( QgsMasterLayoutInterface *masterLayout ) override; + /** + * Populates the specified \a menu with actions reflecting dynamic text expressions applicable for a \a layout. + * + * This includes dynamic text for expressions like: + * + * - current date + * - total page count + * - current page number + * - etc + * + * The \a callback function will be called whenever one of the created actions is triggered. + * + * \since QGIS 3.18 + */ + static void buildInsertDynamicTextMenu( QgsLayout *layout, QMenu *menu, const std::function< void( const QString &expression ) > &callback ); + protected: bool setNewItem( QgsLayoutItem *item ) override; @@ -67,6 +84,8 @@ class GUI_EXPORT QgsLayoutLabelWidget: public QgsLayoutItemBaseWidget, private U QgsLayoutItemPropertiesWidget *mItemPropertiesWidget = nullptr; + QMenu *mDynamicTextMenu = nullptr; + void blockAllSignals( bool block ); }; diff --git a/src/gui/layout/qgslayoutviewtooladditem.cpp b/src/gui/layout/qgslayoutviewtooladditem.cpp index edbf80ea9866..91d1363cd9e8 100644 --- a/src/gui/layout/qgslayoutviewtooladditem.cpp +++ b/src/gui/layout/qgslayoutviewtooladditem.cpp @@ -146,7 +146,7 @@ void QgsLayoutViewToolAddItem::layoutReleaseEvent( QgsLayoutViewMouseEvent *even settings.setEnumValue( QStringLiteral( "LayoutDesigner/lastSizeUnit" ), item->sizeWithUnits().units() ); } - QgsGui::layoutItemGuiRegistry()->newItemAddedToLayout( mItemMetadataId, item ); + QgsGui::layoutItemGuiRegistry()->newItemAddedToLayout( mItemMetadataId, item, mCustomProperties ); // it's possible (in certain circumstances, e.g. when adding frame items) that this item // has already been added to the layout @@ -158,6 +158,12 @@ void QgsLayoutViewToolAddItem::layoutReleaseEvent( QgsLayoutViewMouseEvent *even emit createdItem(); } +void QgsLayoutViewToolAddItem::activate() +{ + QgsLayoutViewTool::activate(); + mCustomProperties.clear(); +} + void QgsLayoutViewToolAddItem::deactivate() { if ( mDrawing ) @@ -170,6 +176,16 @@ void QgsLayoutViewToolAddItem::deactivate() QgsLayoutViewTool::deactivate(); } +QVariantMap QgsLayoutViewToolAddItem::customProperties() const +{ + return mCustomProperties; +} + +void QgsLayoutViewToolAddItem::setCustomProperties( const QVariantMap &customProperties ) +{ + mCustomProperties = customProperties; +} + int QgsLayoutViewToolAddItem::itemMetadataId() const { return mItemMetadataId; diff --git a/src/gui/layout/qgslayoutviewtooladditem.h b/src/gui/layout/qgslayoutviewtooladditem.h index 1c3c891bd499..14ed301c3a41 100644 --- a/src/gui/layout/qgslayoutviewtooladditem.h +++ b/src/gui/layout/qgslayoutviewtooladditem.h @@ -56,8 +56,28 @@ class GUI_EXPORT QgsLayoutViewToolAddItem : public QgsLayoutViewTool void layoutPressEvent( QgsLayoutViewMouseEvent *event ) override; void layoutMoveEvent( QgsLayoutViewMouseEvent *event ) override; void layoutReleaseEvent( QgsLayoutViewMouseEvent *event ) override; + void activate() override; void deactivate() override; + /** + * Returns any custom properties set for the tool. + * + *\see setCustomProperties() + * \since QGIS 3.18 + */ + QVariantMap customProperties() const; + + /** + * Sets custom \a 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. + * + *\see customProperties() + * \since QGIS 3.18 + */ + void setCustomProperties( const QVariantMap &properties ); + signals: /** @@ -83,6 +103,8 @@ class GUI_EXPORT QgsLayoutViewToolAddItem : public QgsLayoutViewTool //! Start of rubber band creation QPointF mRubberBandStartPos; + QVariantMap mCustomProperties; + }; #endif // QGSLAYOUTVIEWTOOLADDITEM_H diff --git a/src/ui/layout/qgslayoutlabelwidgetbase.ui b/src/ui/layout/qgslayoutlabelwidgetbase.ui index 6d74def60864..e702767ce24a 100644 --- a/src/ui/layout/qgslayoutlabelwidgetbase.ui +++ b/src/ui/layout/qgslayoutlabelwidgetbase.ui @@ -6,7 +6,7 @@ 0 0 - 304 + 360 705 @@ -61,8 +61,8 @@ 0 0 - 446 - 665 + 358 + 680 @@ -84,27 +84,49 @@ false - - - - - 0 - 150 - + + + + + 0 + 0 + + + + Dynamic Text + + + QToolButton::InstantPopup - + + + + + 0 + 0 + + + + Insert/Edit Expression… + + + + Render as HTML - - - - Insert or Edit an Expression… + + + + + 0 + 150 + @@ -401,7 +423,7 @@ - + diff --git a/tests/src/gui/testqgslayoutview.cpp b/tests/src/gui/testqgslayoutview.cpp index bfb4bd12f432..03023bbb2af6 100644 --- a/tests/src/gui/testqgslayoutview.cpp +++ b/tests/src/gui/testqgslayoutview.cpp @@ -264,6 +264,7 @@ void TestQgsLayoutView::guiRegistry() // empty registry QVERIFY( !registry.itemMetadata( -1 ) ); QVERIFY( registry.itemMetadataIds().isEmpty() ); + QCOMPARE( registry.metadataIdForItemType( 0 ), -1 ); QVERIFY( !registry.createItemWidget( nullptr ) ); QVERIFY( !registry.createItemWidget( nullptr ) ); std::unique_ptr< TestItem > testItem = qgis::make_unique< TestItem >( nullptr ); @@ -287,6 +288,7 @@ void TestQgsLayoutView::guiRegistry() QCOMPARE( spyTypeAdded.count(), 1 ); int uuid = registry.itemMetadataIds().value( 0 ); QCOMPARE( spyTypeAdded.value( 0 ).at( 0 ).toInt(), uuid ); + QCOMPARE( registry.metadataIdForItemType( QgsLayoutItemRegistry::LayoutItem + 101 ), uuid ); // duplicate type id is allowed metadata = new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutItem + 101, QStringLiteral( "mytype" ), QIcon(), createWidget, createRubberBand ); @@ -295,6 +297,7 @@ void TestQgsLayoutView::guiRegistry() //retrieve metadata QVERIFY( !registry.itemMetadata( -1 ) ); QCOMPARE( registry.itemMetadataIds().count(), 2 ); + QCOMPARE( registry.metadataIdForItemType( QgsLayoutItemRegistry::LayoutItem + 101 ), uuid ); QVERIFY( registry.itemMetadata( uuid ) ); QCOMPARE( registry.itemMetadata( uuid )->visibleName(), QStringLiteral( "mytype" ) );