From cce776c76a54ea3fbc7a44e5675e166b261ced76 Mon Sep 17 00:00:00 2001
From: "r.abdullaev" <caboose7@yandex.ru>
Date: Wed, 25 Sep 2024 09:39:36 +0300
Subject: [PATCH] add possibility to add custom actions for the group-nodes in
 the layer tree

---
 .../gui/auto_generated/qgisinterface.sip.in   | 13 +++
 .../gui/auto_generated/qgisinterface.sip.in   | 13 +++
 src/app/qgisappinterface.cpp                  | 18 +++++
 src/app/qgisappinterface.h                    |  3 +
 src/app/qgsapplayertreeviewmenuprovider.cpp   | 81 +++++++++++++++++--
 src/app/qgsapplayertreeviewmenuprovider.h     |  8 ++
 src/gui/qgisinterface.h                       | 12 +++
 7 files changed, 140 insertions(+), 8 deletions(-)

diff --git a/python/PyQt6/gui/auto_generated/qgisinterface.sip.in b/python/PyQt6/gui/auto_generated/qgisinterface.sip.in
index 04d4e289c1854..4ba0e3dcb508a 100644
--- a/python/PyQt6/gui/auto_generated/qgisinterface.sip.in
+++ b/python/PyQt6/gui/auto_generated/qgisinterface.sip.in
@@ -65,6 +65,11 @@ It is necessary to first call :py:func:`~QgisInterface.addCustomActionForLayerTy
 in order for this method to have any effect.
 
 .. seealso:: :py:func:`addCustomActionForLayerType`
+%End
+
+    virtual void addCustomActionForGroups( QAction *action, QString menu ) = 0;
+%Docstring
+Add action to context menu for all group-nodes in the layer tree.
 %End
 
     virtual bool removeCustomActionForLayerType( QAction *action ) = 0;
@@ -72,6 +77,14 @@ in order for this method to have any effect.
 Remove action for layers in the layer tree previously added with :py:func:`~QgisInterface.addCustomActionForLayerType`
 
 .. seealso:: :py:func:`addCustomActionForLayerType`
+%End
+
+    virtual bool removeCustomActionForGroups( QAction *action ) = 0;
+%Docstring
+Remove action from the context menu for all group-nodes in the layer tree
+previously added with :py:func:`~QgisInterface.addCustomActionForGroups`.
+
+.. seealso:: :py:func:`addCustomActionForGroups`
 %End
 
     virtual QList< QgsMapCanvas * > mapCanvases() = 0;
diff --git a/python/gui/auto_generated/qgisinterface.sip.in b/python/gui/auto_generated/qgisinterface.sip.in
index 04d4e289c1854..4ba0e3dcb508a 100644
--- a/python/gui/auto_generated/qgisinterface.sip.in
+++ b/python/gui/auto_generated/qgisinterface.sip.in
@@ -65,6 +65,11 @@ It is necessary to first call :py:func:`~QgisInterface.addCustomActionForLayerTy
 in order for this method to have any effect.
 
 .. seealso:: :py:func:`addCustomActionForLayerType`
+%End
+
+    virtual void addCustomActionForGroups( QAction *action, QString menu ) = 0;
+%Docstring
+Add action to context menu for all group-nodes in the layer tree.
 %End
 
     virtual bool removeCustomActionForLayerType( QAction *action ) = 0;
@@ -72,6 +77,14 @@ in order for this method to have any effect.
 Remove action for layers in the layer tree previously added with :py:func:`~QgisInterface.addCustomActionForLayerType`
 
 .. seealso:: :py:func:`addCustomActionForLayerType`
+%End
+
+    virtual bool removeCustomActionForGroups( QAction *action ) = 0;
+%Docstring
+Remove action from the context menu for all group-nodes in the layer tree
+previously added with :py:func:`~QgisInterface.addCustomActionForGroups`.
+
+.. seealso:: :py:func:`addCustomActionForGroups`
 %End
 
     virtual QList< QgsMapCanvas * > mapCanvases() = 0;
diff --git a/src/app/qgisappinterface.cpp b/src/app/qgisappinterface.cpp
index ca870fc2040ea..d1ad051c48fa6 100644
--- a/src/app/qgisappinterface.cpp
+++ b/src/app/qgisappinterface.cpp
@@ -101,6 +101,15 @@ void QgisAppInterface::addCustomActionForLayer( QAction *action, QgsMapLayer *la
   menuProvider->addLegendLayerActionForLayer( action, layer );
 }
 
+void QgisAppInterface::addCustomActionForGroups(QAction *action, QString menu )
+{
+  QgsAppLayerTreeViewMenuProvider *menuProvider = dynamic_cast<QgsAppLayerTreeViewMenuProvider *>( qgis->layerTreeView()->menuProvider() );
+  if ( !menuProvider )
+    return;
+
+  menuProvider->addLegendLayerActionForGroup( action, menu );
+}
+
 bool QgisAppInterface::removeCustomActionForLayerType( QAction *action )
 {
   QgsAppLayerTreeViewMenuProvider *menuProvider = dynamic_cast<QgsAppLayerTreeViewMenuProvider *>( qgis->layerTreeView()->menuProvider() );
@@ -110,6 +119,15 @@ bool QgisAppInterface::removeCustomActionForLayerType( QAction *action )
   return menuProvider->removeLegendLayerAction( action );
 }
 
+bool QgisAppInterface::removeCustomActionForGroups( QAction *action )
+{
+    QgsAppLayerTreeViewMenuProvider *menuProvider = dynamic_cast<QgsAppLayerTreeViewMenuProvider *>( qgis->layerTreeView()->menuProvider() );
+    if ( !menuProvider )
+      return false;
+
+    return menuProvider->removeLegendLayerActionForGroup( action );
+}
+
 void QgisAppInterface::zoomFull()
 {
   qgis->zoomFull();
diff --git a/src/app/qgisappinterface.h b/src/app/qgisappinterface.h
index b67407b5d6d9b..e994faef37c73 100644
--- a/src/app/qgisappinterface.h
+++ b/src/app/qgisappinterface.h
@@ -58,7 +58,10 @@ class APP_EXPORT QgisAppInterface : public QgisInterface
     void addCustomActionForLayerType( QAction *action, QString menu,
                                       Qgis::LayerType type, bool allLayers ) override;
     void addCustomActionForLayer( QAction *action, QgsMapLayer *layer ) override;
+    void addCustomActionForGroups( QAction *action, QString menu ) override;
+
     bool removeCustomActionForLayerType( QAction *action ) override;
+    bool removeCustomActionForGroups( QAction *action ) override;
 
     /* Exposed functions */
 
diff --git a/src/app/qgsapplayertreeviewmenuprovider.cpp b/src/app/qgsapplayertreeviewmenuprovider.cpp
index 301a679da8a70..5a01943bcd3f5 100644
--- a/src/app/qgsapplayertreeviewmenuprovider.cpp
+++ b/src/app/qgsapplayertreeviewmenuprovider.cpp
@@ -105,7 +105,10 @@ QMenu *QgsAppLayerTreeViewMenuProvider::createContextMenu()
 
       menu->addAction( actions->actionRenameGroupOrLayer( menu ) );
 
+      addCustomGroupActions( menu );
+
       menu->addSeparator();
+
       menu->addAction( actions->actionAddGroup( menu ) );
       QAction *removeAction = menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRemoveLayer.svg" ) ), tr( "&Remove Group…" ), QgisApp::instance(), &QgisApp::removeLayer );
       removeAction->setEnabled( removeActionEnabled() );
@@ -1014,6 +1017,35 @@ void QgsAppLayerTreeViewMenuProvider::removeLegendLayerActionsForLayer( QgsMapLa
   }
 }
 
+void QgsAppLayerTreeViewMenuProvider::addLegendLayerActionForGroup( QAction *action , const QString &menu )
+{
+  mGroupLegendLayerActionList.append( LegendLayerAction( action, menu, false ) );
+}
+
+bool QgsAppLayerTreeViewMenuProvider::removeLegendLayerActionForGroup( QAction *action )
+{
+  size_t beforeRemoveSize = mGroupLegendLayerActionList.size();
+  mGroupLegendLayerActionList.erase( std::remove_if( mGroupLegendLayerActionList.begin(), mGroupLegendLayerActionList.end(),
+  [&]( const LegendLayerAction& lla )
+  {
+    return lla.action == action;
+  } ), mGroupLegendLayerActionList.end() );
+  size_t afterRemoveSize = mGroupLegendLayerActionList.size();
+  return afterRemoveSize < beforeRemoveSize;
+}
+
+bool QgsAppLayerTreeViewMenuProvider::removeLegendLayerActionsForGroup( const QString &menu )
+{
+  size_t beforeRemoveSize = mGroupLegendLayerActionList.size();
+  mGroupLegendLayerActionList.erase( std::remove_if( mGroupLegendLayerActionList.begin(), mGroupLegendLayerActionList.end(),
+  [&]( const LegendLayerAction& lla )
+  {
+    return lla.menu == menu;
+  } ), mGroupLegendLayerActionList.end() );
+  size_t afterRemoveSize = mGroupLegendLayerActionList.size();
+  return afterRemoveSize < beforeRemoveSize;
+}
+
 QList< LegendLayerAction > QgsAppLayerTreeViewMenuProvider::legendLayerActions( Qgis::LayerType type ) const
 {
 #ifdef QGISDEBUG
@@ -1033,6 +1065,24 @@ QList< LegendLayerAction > QgsAppLayerTreeViewMenuProvider::legendLayerActions(
   return mLegendLayerActionMap.contains( type ) ? mLegendLayerActionMap.value( type ) : QList< LegendLayerAction >();
 }
 
+QList< LegendLayerAction > QgsAppLayerTreeViewMenuProvider::groupLegendLayerActions() const
+{
+  return mGroupLegendLayerActionList;
+}
+
+QList< QAction* > QgsAppLayerTreeViewMenuProvider::groupMenuActions( const QString& menu) const
+{
+  QList< QAction* > actionForGroupMenuList;
+  for (const auto& groupLegendLayerAction : mGroupLegendLayerActionList)
+  {
+    if (groupLegendLayerAction.menu == menu)
+    {
+      actionForGroupMenuList.push_back(groupLegendLayerAction.action);
+    }
+  }
+  return actionForGroupMenuList;
+}
+
 void QgsAppLayerTreeViewMenuProvider::addCustomLayerActions( QMenu *menu, QgsMapLayer *layer )
 {
   if ( !layer )
@@ -1043,21 +1093,38 @@ void QgsAppLayerTreeViewMenuProvider::addCustomLayerActions( QMenu *menu, QgsMap
 
   if ( ! lyrActions.isEmpty() )
   {
+      addCustomActionsToMenu( menu, lyrActions );
+  }
+}
+
+void QgsAppLayerTreeViewMenuProvider::addCustomGroupActions(QMenu *menu)
+{
+  // add custom group actions - should this go at end?
+  QList< LegendLayerAction > groupActions = groupLegendLayerActions();
+
+  if ( ! groupActions.isEmpty() )
+  {
+    addCustomActionsToMenu( menu, groupActions );
+  }
+
+}
+
+void QgsAppLayerTreeViewMenuProvider::addCustomActionsToMenu(QMenu *menu, const QList<LegendLayerAction> &customActions)
+{
     menu->addSeparator();
     QList<QMenu *> menus;
-    for ( int i = 0; i < lyrActions.count(); i++ )
+    for ( int i = 0; i < customActions.count(); i++ )
     {
-      if ( lyrActions[i].allLayers || lyrActions[i].layers.contains( layer ) )
       {
-        if ( lyrActions[i].menu.isEmpty() )
+        if ( customActions[i].menu.isEmpty() )
         {
-          menu->addAction( lyrActions[i].action );
+          menu->addAction( customActions[i].action );
         }
         else
         {
           // find or create menu for given menu name
           // adapted from QgisApp::getPluginMenu( QString menuName )
-          QString menuName = lyrActions[i].menu;
+          QString menuName = customActions[i].menu;
 #ifdef Q_OS_MAC
           // Mac doesn't have '&' keyboard shortcuts.
           menuName.remove( QChar( '&' ) );
@@ -1093,13 +1160,11 @@ void QgsAppLayerTreeViewMenuProvider::addCustomLayerActions( QMenu *menu, QgsMap
             // Where to put it? - we worked that out above...
             menu->insertMenu( before, newMenu );
           }
-          // QMenu* menu = getMenu( lyrActions[i].menu, &beforeSep, &afterSep, &menu );
-          newMenu->addAction( lyrActions[i].action );
+          newMenu->addAction( customActions[i].action );
         }
       }
     }
     menu->addSeparator();
-  }
 }
 
 void QgsAppLayerTreeViewMenuProvider::editVectorSymbol( const QString &layerId )
diff --git a/src/app/qgsapplayertreeviewmenuprovider.h b/src/app/qgsapplayertreeviewmenuprovider.h
index e3b7afabf76e2..ee69de56c3900 100644
--- a/src/app/qgsapplayertreeviewmenuprovider.h
+++ b/src/app/qgsapplayertreeviewmenuprovider.h
@@ -51,16 +51,24 @@ class QgsAppLayerTreeViewMenuProvider : public QObject, public QgsLayerTreeViewM
     bool removeLegendLayerAction( QAction *action );
     void addLegendLayerActionForLayer( QAction *action, QgsMapLayer *layer );
     void removeLegendLayerActionsForLayer( QgsMapLayer *layer );
+    void addLegendLayerActionForGroup( QAction *action, const QString &menu );
+    bool removeLegendLayerActionForGroup( QAction *action );
+    bool removeLegendLayerActionsForGroup( const QString& menu );
     QList< LegendLayerAction > legendLayerActions( Qgis::LayerType type ) const;
+    QList< LegendLayerAction > groupLegendLayerActions() const;
+    QList< QAction* > groupMenuActions( const QString& menu ) const;
 
   protected:
 
     void addCustomLayerActions( QMenu *menu, QgsMapLayer *layer );
+    void addCustomGroupActions( QMenu *menu );
+    void addCustomActionsToMenu( QMenu *menu, const QList< LegendLayerAction >& customActions );
 
     QgsLayerTreeView *mView = nullptr;
     QgsMapCanvas *mCanvas = nullptr;
 
     QMap< Qgis::LayerType, QList< LegendLayerAction > > mLegendLayerActionMap;
+    QList< LegendLayerAction > mGroupLegendLayerActionList;
 
   private slots:
 
diff --git a/src/gui/qgisinterface.h b/src/gui/qgisinterface.h
index 4c454e6c3ba26..f8dc27168772b 100644
--- a/src/gui/qgisinterface.h
+++ b/src/gui/qgisinterface.h
@@ -129,12 +129,24 @@ class GUI_EXPORT QgisInterface : public QObject
      */
     virtual void addCustomActionForLayer( QAction *action, QgsMapLayer *layer ) = 0;
 
+    /**
+     * Add action to context menu for all group-nodes in the layer tree.
+     */
+    virtual void addCustomActionForGroups( QAction *action, QString menu ) = 0;
+
     /**
      * Remove action for layers in the layer tree previously added with addCustomActionForLayerType()
      * \see addCustomActionForLayerType()
      */
     virtual bool removeCustomActionForLayerType( QAction *action ) = 0;
 
+    /**
+     * Remove action from the context menu for all group-nodes in the layer tree
+     * previously added with addCustomActionForGroups().
+     * \see addCustomActionForGroups()
+     */
+    virtual bool removeCustomActionForGroups( QAction *action ) = 0;
+
     /**
      * Returns a list of all map canvases open in the app.
      */