From 7f066672b3ea9982fc57e9f58d90f5231b75d383 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 18 Jul 2017 12:36:16 +1000 Subject: [PATCH] Add method to specify item groups for item classes in QgsLayoutItemGuiRegistry This allows the designer dialog to group the corresponding item actions together (i.e. grouping all basic shape creation actions together), but without any hardcoded special handling so that plugin based items can also be grouped. --- .../gui/layout/qgslayoutitemguiregistry.sip | 71 +++++++++++++++- src/app/layout/qgslayoutdesignerdialog.cpp | 54 ++++++++++++- src/app/layout/qgslayoutdesignerdialog.h | 4 + src/gui/layout/qgslayoutitemguiregistry.cpp | 22 ++++- src/gui/layout/qgslayoutitemguiregistry.h | 80 ++++++++++++++++++- tests/src/gui/testqgslayoutview.cpp | 6 ++ 6 files changed, 228 insertions(+), 9 deletions(-) diff --git a/python/gui/layout/qgslayoutitemguiregistry.sip b/python/gui/layout/qgslayoutitemguiregistry.sip index 059bcd09860..67de0cd3cb3 100644 --- a/python/gui/layout/qgslayoutitemguiregistry.sip +++ b/python/gui/layout/qgslayoutitemguiregistry.sip @@ -28,9 +28,11 @@ class QgsLayoutItemAbstractGuiMetadata %End public: - QgsLayoutItemAbstractGuiMetadata( int type ); + QgsLayoutItemAbstractGuiMetadata( int type, const QString &groupId = QString() ); %Docstring Constructor for QgsLayoutItemAbstractGuiMetadata with the specified class ``type``. + + An optional ``groupId`` can be set, which allows grouping of related layout item classes. See QgsLayoutItemGuiMetadata for details. %End virtual ~QgsLayoutItemAbstractGuiMetadata(); @@ -41,6 +43,12 @@ class QgsLayoutItemAbstractGuiMetadata :rtype: int %End + QString groupId() const; +%Docstring + Returns the item group ID, if set. + :rtype: str +%End + virtual QIcon creationIcon() const; %Docstring Returns an icon representing creation of the layout item type. @@ -65,6 +73,48 @@ class QgsLayoutItemAbstractGuiMetadata +class QgsLayoutItemGuiGroup +{ +%Docstring + Stores GUI metadata about a group of layout item classes. + + QgsLayoutItemGuiGroup stores settings about groups of related layout item classes + which should be presented to users grouped together. + + For instance, the various basic shape creation tools would use QgsLayoutItemGuiGroup + to display grouped within designer dialogs. + +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgslayoutitemguiregistry.h" +%End + public: + + QgsLayoutItemGuiGroup( const QString &id = QString(), const QString &name = QString(), const QIcon &icon = QIcon() ); +%Docstring + Constructor for QgsLayoutItemGuiGroup. +%End + + QString id; +%Docstring + Unique (untranslated) group ID string. +%End + + QString name; +%Docstring + Translated group name. +%End + + QIcon icon; +%Docstring + Icon for group. +%End + +}; + + class QgsLayoutItemGuiRegistry : QObject { %Docstring @@ -117,6 +167,25 @@ class QgsLayoutItemGuiRegistry : QObject :rtype: bool %End + bool addItemGroup( const QgsLayoutItemGuiGroup &group ); +%Docstring + Registers a new item group with the registry. This must be done before calling + addLayoutItemGuiMetadata() for any item types associated with the group. + + Returns true if group was added, or false if group could not be added (e.g. due to + duplicate id value). + +.. seealso:: itemGroup() + :rtype: bool +%End + + const QgsLayoutItemGuiGroup &itemGroup( const QString &id ); +%Docstring + Returns a reference to the item group with matching ``id``. +.. seealso:: addItemGroup() + :rtype: QgsLayoutItemGuiGroup +%End + QWidget *createItemWidget( int type ) const /Factory/; %Docstring Creates a new instance of a layout item configuration widget for the specified item ``type``. diff --git a/src/app/layout/qgslayoutdesignerdialog.cpp b/src/app/layout/qgslayoutdesignerdialog.cpp index f987818cf79..9bb9eb58fb5 100644 --- a/src/app/layout/qgslayoutdesignerdialog.cpp +++ b/src/app/layout/qgslayoutdesignerdialog.cpp @@ -301,15 +301,65 @@ void QgsLayoutDesignerDialog::closeEvent( QCloseEvent * ) void QgsLayoutDesignerDialog::itemTypeAdded( int type ) { QString name = QgsApplication::layoutItemRegistry()->itemMetadata( type )->visibleName(); + QString groupId = QgsGui::layoutItemGuiRegistry()->itemMetadata( type )->groupId(); + QToolButton *groupButton = nullptr; + QMenu *itemSubmenu = nullptr; + if ( !groupId.isEmpty() ) + { + // find existing group toolbutton and submenu, or create new ones if this is the first time the group has been encountered + const QgsLayoutItemGuiGroup &group = QgsGui::layoutItemGuiRegistry()->itemGroup( groupId ); + QIcon groupIcon = group.icon.isNull() ? QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicShape.svg" ) ) : group.icon; + QString groupText = tr( "Add %1" ).arg( group.name ); + if ( mItemGroupToolButtons.contains( groupId ) ) + { + groupButton = mItemGroupToolButtons.value( groupId ); + } + else + { + QToolButton *groupToolButton = new QToolButton( mToolsToolbar ); + groupToolButton->setIcon( groupIcon ); + groupToolButton->setCheckable( true ); + groupToolButton->setPopupMode( QToolButton::InstantPopup ); + groupToolButton->setAutoRaise( true ); + groupToolButton->setToolButtonStyle( Qt::ToolButtonIconOnly ); + groupToolButton->setToolTip( groupText ); + mToolsToolbar->addWidget( groupToolButton ); + mItemGroupToolButtons.insert( groupId, groupToolButton ); + groupButton = groupToolButton; + } + + if ( mItemGroupSubmenus.contains( groupId ) ) + { + itemSubmenu = mItemGroupSubmenus.value( groupId ); + } + else + { + QMenu *groupSubmenu = mItemMenu->addMenu( groupText ); + groupSubmenu->setIcon( groupIcon ); + mItemMenu->addMenu( groupSubmenu ); + mItemGroupSubmenus.insert( groupId, groupSubmenu ); + itemSubmenu = groupSubmenu; + } + } + // update UI for new item type QAction *action = new QAction( tr( "Add %1" ).arg( name ), this ); action->setToolTip( tr( "Adds a new %1 to the layout" ).arg( name ) ); action->setCheckable( true ); action->setData( type ); action->setIcon( QgsGui::layoutItemGuiRegistry()->itemMetadata( type )->creationIcon() ); + mToolsActionGroup->addAction( action ); - mItemMenu->addAction( action ); - mToolsToolbar->addAction( action ); + if ( itemSubmenu ) + itemSubmenu->addAction( action ); + else + mItemMenu->addAction( action ); + + if ( groupButton ) + groupButton->addAction( action ); + else + mToolsToolbar->addAction( action ); + connect( action, &QAction::triggered, this, [this, type]() { activateNewItemCreationTool( type ); diff --git a/src/app/layout/qgslayoutdesignerdialog.h b/src/app/layout/qgslayoutdesignerdialog.h index a97ef467281..6f919269708 100644 --- a/src/app/layout/qgslayoutdesignerdialog.h +++ b/src/app/layout/qgslayoutdesignerdialog.h @@ -19,6 +19,7 @@ #include "ui_qgslayoutdesignerbase.h" #include "qgslayoutdesignerinterface.h" +#include class QgsLayoutDesignerDialog; class QgsLayoutView; @@ -161,6 +162,9 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner QgsLayoutViewToolZoom *mZoomTool = nullptr; QgsLayoutViewToolSelect *mSelectTool = nullptr; + QMap< QString, QToolButton * > mItemGroupToolButtons; + QMap< QString, QMenu * > mItemGroupSubmenus; + //! Save window state void saveWindowState(); diff --git a/src/gui/layout/qgslayoutitemguiregistry.cpp b/src/gui/layout/qgslayoutitemguiregistry.cpp index 09e58c892f4..7d2022c8b82 100644 --- a/src/gui/layout/qgslayoutitemguiregistry.cpp +++ b/src/gui/layout/qgslayoutitemguiregistry.cpp @@ -41,6 +41,8 @@ bool QgsLayoutItemGuiRegistry::populate() if ( !mMetadata.isEmpty() ) return false; + addItemGroup( QgsLayoutItemGuiGroup( QStringLiteral( "shapes" ), tr( "Shape" ), QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicShape.svg" ) ) ) ); + auto createRubberBand = ( []( QgsLayoutView * view )->QgsLayoutViewRubberBand * { return new QgsLayoutViewRectangularRubberBand( view ); @@ -55,9 +57,9 @@ bool QgsLayoutItemGuiRegistry::populate() } ); addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( 101, QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddLabel.svg" ) ), nullptr, createRubberBand ) ); - addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutRectangle, QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicRectangle.svg" ) ), nullptr, createRubberBand ) ); - addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutEllipse, QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicCircle.svg" ) ), nullptr, createEllipseBand ) ); - addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutTriangle, QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicTriangle.svg" ) ), nullptr, createTriangleBand ) ); + addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutRectangle, QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicRectangle.svg" ) ), nullptr, createRubberBand, QStringLiteral( "shapes" ) ) ); + addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutEllipse, QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicCircle.svg" ) ), nullptr, createEllipseBand, QStringLiteral( "shapes" ) ) ); + addLayoutItemGuiMetadata( new QgsLayoutItemGuiMetadata( QgsLayoutItemRegistry::LayoutTriangle, QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddBasicTriangle.svg" ) ), nullptr, createTriangleBand, QStringLiteral( "shapes" ) ) ); return true; } @@ -76,6 +78,20 @@ bool QgsLayoutItemGuiRegistry::addLayoutItemGuiMetadata( QgsLayoutItemAbstractGu return true; } +bool QgsLayoutItemGuiRegistry::addItemGroup( const QgsLayoutItemGuiGroup &group ) +{ + if ( mItemGroups.contains( group.id ) ) + return false; + + mItemGroups.insert( group.id, group ); + return true; +} + +const QgsLayoutItemGuiGroup &QgsLayoutItemGuiRegistry::itemGroup( const QString &id ) +{ + return mItemGroups[ id ]; +} + QWidget *QgsLayoutItemGuiRegistry::createItemWidget( int type ) const { if ( !mMetadata.contains( type ) ) diff --git a/src/gui/layout/qgslayoutitemguiregistry.h b/src/gui/layout/qgslayoutitemguiregistry.h index 8c0c1147551..546f8d04a0e 100644 --- a/src/gui/layout/qgslayoutitemguiregistry.h +++ b/src/gui/layout/qgslayoutitemguiregistry.h @@ -47,9 +47,12 @@ class GUI_EXPORT QgsLayoutItemAbstractGuiMetadata /** * Constructor for QgsLayoutItemAbstractGuiMetadata with the specified class \a type. + * + * An optional \a groupId can be set, which allows grouping of related layout item classes. See QgsLayoutItemGuiMetadata for details. */ - QgsLayoutItemAbstractGuiMetadata( int type ) + QgsLayoutItemAbstractGuiMetadata( int type, const QString &groupId = QString() ) : mType( type ) + , mGroupId( groupId ) {} virtual ~QgsLayoutItemAbstractGuiMetadata() = default; @@ -59,6 +62,11 @@ class GUI_EXPORT QgsLayoutItemAbstractGuiMetadata */ int type() const { return mType; } + /** + * Returns the item group ID, if set. + */ + QString groupId() const { return mGroupId; } + /** * Returns an icon representing creation of the layout item type. */ @@ -78,6 +86,8 @@ class GUI_EXPORT QgsLayoutItemAbstractGuiMetadata private: int mType = -1; + QString mGroupId; + }; //! Layout item configuration widget creation function @@ -102,11 +112,13 @@ class GUI_EXPORT QgsLayoutItemGuiMetadata : public QgsLayoutItemAbstractGuiMetad * Constructor for QgsLayoutItemGuiMetadata with the specified class \a type * and \a creationIcon, and function pointers for the various * configuration widget creation functions. + * + * An optional \a groupId can be set, which allows grouping of related layout item classes. See QgsLayoutItemGuiMetadata for details. */ QgsLayoutItemGuiMetadata( int type, const QIcon &creationIcon, QgsLayoutItemWidgetFunc pfWidget = nullptr, - QgsLayoutItemRubberBandFunc pfRubberBand = nullptr ) - : QgsLayoutItemAbstractGuiMetadata( type ) + QgsLayoutItemRubberBandFunc pfRubberBand = nullptr, const QString &groupId = QString() ) + : QgsLayoutItemAbstractGuiMetadata( type, groupId ) , mIcon( creationIcon ) , mWidgetFunc( pfWidget ) , mRubberBandFunc( pfRubberBand ) @@ -149,6 +161,49 @@ class GUI_EXPORT QgsLayoutItemGuiMetadata : public QgsLayoutItemAbstractGuiMetad #endif +/** + * \ingroup gui + * \brief Stores GUI metadata about a group of layout item classes. + * + * QgsLayoutItemGuiGroup stores settings about groups of related layout item classes + * which should be presented to users grouped together. + * + * For instance, the various basic shape creation tools would use QgsLayoutItemGuiGroup + * to display grouped within designer dialogs. + * + * \since QGIS 3.0 + */ +class GUI_EXPORT QgsLayoutItemGuiGroup +{ + public: + + /** + * Constructor for QgsLayoutItemGuiGroup. + */ + QgsLayoutItemGuiGroup( const QString &id = QString(), const QString &name = QString(), const QIcon &icon = QIcon() ) + : id( id ) + , name( name ) + , icon( icon ) + {} + + /** + * Unique (untranslated) group ID string. + */ + QString id; + + /** + * Translated group name. + */ + QString name; + + /** + * Icon for group. + */ + QIcon icon; + +}; + + /** * \ingroup core * \class QgsLayoutItemGuiRegistry @@ -202,6 +257,23 @@ class GUI_EXPORT QgsLayoutItemGuiRegistry : public QObject */ bool addLayoutItemGuiMetadata( QgsLayoutItemAbstractGuiMetadata *metadata SIP_TRANSFER ); + /** + * Registers a new item group with the registry. This must be done before calling + * addLayoutItemGuiMetadata() for any item types associated with the group. + * + * Returns true if group was added, or false if group could not be added (e.g. due to + * duplicate id value). + * + * \see itemGroup() + */ + bool addItemGroup( const QgsLayoutItemGuiGroup &group ); + + /** + * Returns a reference to the item group with matching \a id. + * \see addItemGroup() + */ + const QgsLayoutItemGuiGroup &itemGroup( const QString &id ); + /** * Creates a new instance of a layout item configuration widget for the specified item \a type. */ @@ -233,6 +305,8 @@ class GUI_EXPORT QgsLayoutItemGuiRegistry : public QObject QMap mMetadata; + QMap< QString, QgsLayoutItemGuiGroup > mItemGroups; + }; #endif //QGSLAYOUTITEMGUIREGISTRY_H diff --git a/tests/src/gui/testqgslayoutview.cpp b/tests/src/gui/testqgslayoutview.cpp index 096dca3877b..608a3a81085 100644 --- a/tests/src/gui/testqgslayoutview.cpp +++ b/tests/src/gui/testqgslayoutview.cpp @@ -300,6 +300,12 @@ void TestQgsLayoutView::guiRegistry() QCOMPARE( band->view(), view ); delete band; + // groups + QVERIFY( registry.addItemGroup( QgsLayoutItemGuiGroup( QStringLiteral( "g1" ) ) ) ); + QCOMPARE( registry.itemGroup( QStringLiteral( "g1" ) ).id, QStringLiteral( "g1" ) ); + // can't add duplicate group + QVERIFY( !registry.addItemGroup( QgsLayoutItemGuiGroup( QStringLiteral( "g1" ) ) ) ); + //test populate QgsLayoutItemGuiRegistry reg2; QVERIFY( reg2.itemTypes().isEmpty() );