1 /***************************************************************************
2     qgsapplayertreeviewmenuprovider.cpp
3     ---------------------
4     begin                : May 2014
5     copyright            : (C) 2014 by Martin Dobias
6     email                : wonder dot sk at gmail dot com
7  ***************************************************************************
8  *                                                                         *
9  *   This program is free software; you can redistribute it and/or modify  *
10  *   it under the terms of the GNU General Public License as published by  *
11  *   the Free Software Foundation; either version 2 of the License, or     *
12  *   (at your option) any later version.                                   *
13  *                                                                         *
14  ***************************************************************************/
15 #include <QClipboard>
16 #include <QMessageBox>
17 
18 #include "qgisapp.h"
19 #include "qgsapplayertreeviewmenuprovider.h"
20 #include "qgsapplication.h"
21 #include "qgsclipboard.h"
22 #include "qgscolorschemeregistry.h"
23 #include "qgscolorswatchgrid.h"
24 #include "qgscolorwidgets.h"
25 #include "qgsdialog.h"
26 #include "qgsgui.h"
27 #include "qgslayernotesmanager.h"
28 #include "qgslayernotesutils.h"
29 #include "qgslayertree.h"
30 #include "qgslayertreemodel.h"
31 #include "qgslayertreemodellegendnode.h"
32 #include "qgslayertreeregistrybridge.h"
33 #include "qgslayertreeviewdefaultactions.h"
34 #include "qgsmapcanvas.h"
35 #include "qgsmaplayerstylecategoriesmodel.h"
36 #include "qgsmaplayerstyleguiutils.h"
37 #include "qgsmaplayerutils.h"
38 #include "qgsmessagebar.h"
39 #include "qgspointcloudlayer.h"
40 #include "qgsproject.h"
41 #include "qgsqueryresultwidget.h"
42 #include "qgsrasterlayer.h"
43 #include "qgsrenderer.h"
44 #include "qgssinglesymbolrenderer.h"
45 #include "qgsstyle.h"
46 #include "qgssymbol.h"
47 #include "qgssymbollayerutils.h"
48 #include "qgssymbolselectordialog.h"
49 #include "qgsvectordataprovider.h"
50 #include "qgsvectorlayer.h"
51 #include "qgsvectorlayerlabeling.h"
52 #include "qgsxmlutils.h"
53 #include "qgsmeshlayer.h"
54 
55 
QgsAppLayerTreeViewMenuProvider(QgsLayerTreeView * view,QgsMapCanvas * canvas)56 QgsAppLayerTreeViewMenuProvider::QgsAppLayerTreeViewMenuProvider( QgsLayerTreeView *view, QgsMapCanvas *canvas )
57   : mView( view )
58   , mCanvas( canvas )
59 {
60 }
61 
createContextMenu()62 QMenu *QgsAppLayerTreeViewMenuProvider::createContextMenu()
63 {
64   QMenu *menu = new QMenu;
65 
66   QgsLayerTreeViewDefaultActions *actions = mView->defaultActions();
67 
68   const QModelIndex idx = mView->currentIndex();
69   if ( !idx.isValid() )
70   {
71     // global menu
72     menu->addAction( actions->actionAddGroup( menu ) );
73     menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionExpandTree.svg" ) ), tr( "&Expand All" ), mView, &QgsLayerTreeView::expandAll );
74     menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionCollapseTree.svg" ) ), tr( "&Collapse All" ), mView, &QgsLayerTreeView::collapseAll );
75     menu->addSeparator();
76     if ( QgisApp::instance()->clipboard()->hasFormat( QGSCLIPBOARD_MAPLAYER_MIME ) )
77     {
78       QAction *actionPasteLayerOrGroup = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionEditPaste.svg" ) ), tr( "Paste Layer/Group" ), menu );
79       connect( actionPasteLayerOrGroup, &QAction::triggered, QgisApp::instance(), &QgisApp::pasteLayer );
80       menu->addAction( actionPasteLayerOrGroup );
81     }
82 
83     // TODO: update drawing order
84   }
85   else if ( QgsLayerTreeNode *node = mView->index2node( idx ) )
86   {
87     // layer or group selected
88     if ( QgsLayerTree::isGroup( node ) )
89     {
90       menu->addAction( actions->actionZoomToGroup( mCanvas, menu ) );
91 
92       menu->addAction( tr( "Co&py Group" ), QgisApp::instance(), &QgisApp::copyLayer );
93       if ( QgisApp::instance()->clipboard()->hasFormat( QGSCLIPBOARD_MAPLAYER_MIME ) )
94       {
95         QAction *actionPasteLayerOrGroup = new QAction( tr( "Paste Layer/Group" ), menu );
96         connect( actionPasteLayerOrGroup, &QAction::triggered, QgisApp::instance(), &QgisApp::pasteLayer );
97         menu->addAction( actionPasteLayerOrGroup );
98       }
99 
100       menu->addAction( actions->actionRenameGroupOrLayer( menu ) );
101 
102       menu->addSeparator();
103       menu->addAction( actions->actionAddGroup( menu ) );
104       QAction *removeAction = menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRemoveLayer.svg" ) ), tr( "&Remove Group…" ), QgisApp::instance(), &QgisApp::removeLayer );
105       removeAction->setEnabled( removeActionEnabled() );
106       menu->addSeparator();
107 
108       menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSetCRS.png" ) ),
109                        tr( "Set Group &CRS…" ), QgisApp::instance(), &QgisApp::legendGroupSetCrs );
110       menu->addAction( tr( "Set Group &WMS Data…" ), QgisApp::instance(), &QgisApp::legendGroupSetWmsData );
111 
112       menu->addSeparator();
113 
114       menu->addAction( actions->actionMutuallyExclusiveGroup( menu ) );
115 
116       if ( QAction *checkAll = actions->actionCheckAndAllChildren( menu ) )
117         menu->addAction( checkAll );
118 
119       if ( QAction *unCheckAll = actions->actionUncheckAndAllChildren( menu ) )
120         menu->addAction( unCheckAll );
121 
122       if ( !( mView->selectedNodes( true ).count() == 1 && idx.row() == 0 ) )
123       {
124         menu->addAction( actions->actionMoveToTop( menu ) );
125       }
126 
127       if ( !( mView->selectedNodes( true ).count() == 1 && idx.row() == idx.model()->rowCount() - 1 ) )
128       {
129         menu->addAction( actions->actionMoveToBottom( menu ) );
130       }
131 
132       menu->addSeparator();
133 
134       if ( mView->selectedNodes( true ).count() >= 2 )
135         menu->addAction( actions->actionGroupSelected( menu ) );
136 
137       if ( QgisApp::instance()->clipboard()->hasFormat( QGSCLIPBOARD_STYLE_MIME ) )
138       {
139         menu->addAction( tr( "Paste Style" ), QgisApp::instance(), &QgisApp::applyStyleToGroup );
140       }
141 
142       menu->addSeparator();
143 
144       QMenu *menuExportGroup = new QMenu( tr( "E&xport" ), menu );
145       QAction *actionSaveAsDefinitionGroup = new QAction( tr( "Save as Layer &Definition File…" ), menuExportGroup );
146       connect( actionSaveAsDefinitionGroup, &QAction::triggered, QgisApp::instance(), &QgisApp::saveAsLayerDefinition );
147       menuExportGroup->addAction( actionSaveAsDefinitionGroup );
148 
149       menu->addMenu( menuExportGroup );
150     }
151     else if ( QgsLayerTree::isLayer( node ) )
152     {
153       QgsMapLayer *layer = QgsLayerTree::toLayer( node )->layer();
154       QgsRasterLayer *rlayer = qobject_cast<QgsRasterLayer *>( layer );
155       QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
156       QgsPointCloudLayer *pcLayer = qobject_cast<QgsPointCloudLayer * >( layer );
157       QgsMeshLayer *meshLayer = qobject_cast<QgsMeshLayer * >( layer );
158 
159       if ( layer && layer->isSpatial() )
160       {
161 
162         QAction *zoomToLayers = actions->actionZoomToLayers( mCanvas, menu );
163         zoomToLayers->setEnabled( layer->isValid() );
164         menu->addAction( zoomToLayers );
165         if ( vlayer )
166         {
167           const QList<QgsMapLayer *> selectedLayers = mView->selectedLayers();
168           bool hasSelectedFeature = false;
169           for ( const QgsMapLayer *layer : selectedLayers )
170           {
171 
172             if ( const QgsVectorLayer *vLayer = qobject_cast<const QgsVectorLayer *>( layer ) )
173             {
174 
175               if ( vLayer->selectedFeatureCount() > 0 )
176               {
177                 hasSelectedFeature = true;
178                 break;
179               }
180             }
181 
182           }
183           QAction *actionZoomSelected = actions->actionZoomToSelection( mCanvas, menu );
184           actionZoomSelected->setEnabled( vlayer->isValid() && hasSelectedFeature );
185           menu->addAction( actionZoomSelected );
186         }
187         menu->addAction( actions->actionShowInOverview( menu ) );
188       }
189 
190       if ( vlayer )
191       {
192         QAction *showFeatureCount = actions->actionShowFeatureCount( menu );
193         menu->addAction( showFeatureCount );
194         showFeatureCount->setEnabled( vlayer->isValid() );
195 
196         const QString iconName = vlayer && vlayer->labeling() && vlayer->labeling()->type() == QLatin1String( "rule-based" )
197                                  ? QStringLiteral( "labelingRuleBased.svg" )
198                                  : QStringLiteral( "labelingSingle.svg" );
199         QAction *actionShowLabels = new QAction( QgsApplication::getThemeIcon( iconName ), tr( "Show &Labels" ), menu );
200         actionShowLabels->setCheckable( true );
201         actionShowLabels->setChecked( vlayer->labelsEnabled() );
202         connect( actionShowLabels, &QAction::toggled, this, &QgsAppLayerTreeViewMenuProvider::toggleLabels );
203         menu->addAction( actionShowLabels );
204       }
205 
206       QAction *actionCopyLayer = new QAction( tr( "Copy Layer" ), menu );
207       connect( actionCopyLayer, &QAction::triggered, QgisApp::instance(), &QgisApp::copyLayer );
208       menu->addAction( actionCopyLayer );
209 
210       menu->addAction( actions->actionRenameGroupOrLayer( menu ) );
211 
212       if ( rlayer )
213       {
214         QAction *zoomToNative = menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionZoomActual.svg" ) ), tr( "Zoom to Nat&ive Resolution (100%)" ), QgisApp::instance(), &QgisApp::legendLayerZoomNative );
215         zoomToNative->setEnabled( rlayer->isValid() );
216 
217         if ( rlayer->rasterType() != QgsRasterLayer::Palette )
218         {
219           QAction *stretch = menu->addAction( tr( "&Stretch Using Current Extent" ), QgisApp::instance(), &QgisApp::legendLayerStretchUsingCurrentExtent );
220           stretch->setEnabled( rlayer->isValid() );
221         }
222       }
223 
224       // No raster support in createSqlVectorLayer (yet)
225       if ( vlayer )
226       {
227         const std::unique_ptr< QgsAbstractDatabaseProviderConnection> conn { QgsMapLayerUtils::databaseConnection( layer ) };
228         if ( conn )
229           menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/dbmanager.svg" ) ), tr( "Update SQL Layer…" ), menu, [ layer, this ]
230         {
231           std::unique_ptr< QgsAbstractDatabaseProviderConnection> conn2 { QgsMapLayerUtils::databaseConnection( layer ) };
232           if ( conn2 )
233           {
234             QgsDialog dialog;
235             dialog.setObjectName( QStringLiteral( "SqlUpdateDialog" ) );
236             dialog.setWindowTitle( tr( "%1 — Update SQL" ).arg( layer->name() ) );
237             QgsGui::enableAutoGeometryRestore( &dialog );
238             QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions options { conn2->sqlOptions( layer->source() ) };
239             options.layerName = layer->name();
240             QgsQueryResultWidget *queryResultWidget { new QgsQueryResultWidget( &dialog, conn2.release() ) };
241             queryResultWidget->setWidgetMode( QgsQueryResultWidget::QueryWidgetMode::QueryLayerUpdateMode );
242             queryResultWidget->setSqlVectorLayerOptions( options );
243             queryResultWidget->executeQuery();
244             queryResultWidget->layout()->setMargin( 0 );
245             dialog.layout()->addWidget( queryResultWidget );
246 
247             connect( queryResultWidget, &QgsQueryResultWidget::createSqlVectorLayer, queryResultWidget, [queryResultWidget, layer, this ]( const QString &, const QString &, const QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions & options )
248             {
249               ( void )this;
250               std::unique_ptr< QgsAbstractDatabaseProviderConnection> conn3 { QgsMapLayerUtils::databaseConnection( layer ) };
251               if ( conn3 )
252               {
253                 try
254                 {
255                   std::unique_ptr<QgsMapLayer> sqlLayer { conn3->createSqlVectorLayer( options ) };
256                   if ( sqlLayer->isValid() )
257                   {
258                     layer->setDataSource( sqlLayer->source(), sqlLayer->name(), sqlLayer->dataProvider()->name(), QgsDataProvider::ProviderOptions() );
259                     queryResultWidget->notify( QObject::tr( "Layer Update Success" ), QObject::tr( "The SQL layer was updated successfully" ), Qgis::MessageLevel::Success );
260                   }
261                   else
262                   {
263                     QString error { sqlLayer->dataProvider()->error().message( QgsErrorMessage::Format::Text ) };
264                     if ( error.isEmpty() )
265                     {
266                       error = QObject::tr( "layer is not valid, check the log messages for more information" );
267                     }
268                     queryResultWidget->notify( QObject::tr( "Layer Update Error" ), QObject::tr( "Error updating the SQL layer: %1" ).arg( error ), Qgis::MessageLevel::Critical );
269                   }
270                 }
271                 catch ( QgsProviderConnectionException &ex )
272                 {
273                   queryResultWidget->notify( QObject::tr( "Layer Update Error" ), QObject::tr( "Error updating the SQL layer: %1" ).arg( ex.what() ), Qgis::MessageLevel::Critical );
274                 }
275               }
276 
277             } );
278 
279             dialog.exec();
280 
281           }
282         } );
283       }
284 
285       addCustomLayerActions( menu, layer );
286       if ( layer && layer->type() == QgsMapLayerType::VectorLayer && static_cast<QgsVectorLayer *>( layer )->providerType() == QLatin1String( "virtual" ) )
287       {
288         menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionAddVirtualLayer.svg" ) ), tr( "Edit Virtual Layer…" ), QgisApp::instance(), &QgisApp::addVirtualLayer );
289       }
290 
291       menu->addSeparator();
292 
293       // duplicate layer
294       QAction *duplicateLayersAction = menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDuplicateLayer.svg" ) ), tr( "&Duplicate Layer" ), QgisApp::instance(), [] { QgisApp::instance()->duplicateLayers(); } );
295       QAction *removeAction = menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRemoveLayer.svg" ) ), tr( "&Remove Layer…" ), QgisApp::instance(), &QgisApp::removeLayer );
296       removeAction->setEnabled( removeActionEnabled() );
297 
298       menu->addSeparator();
299 
300       if ( node->parent() != mView->layerTreeModel()->rootGroup() )
301         menu->addAction( actions->actionMoveOutOfGroup( menu ) );
302 
303       if ( !( mView->selectedNodes( true ).count() == 1 && idx.row() == 0 ) )
304       {
305         menu->addAction( actions->actionMoveToTop( menu ) );
306       }
307 
308       if ( !( mView->selectedNodes( true ).count() == 1 && idx.row() == idx.model()->rowCount() - 1 ) )
309       {
310         menu->addAction( actions->actionMoveToBottom( menu ) );
311       }
312 
313       QAction *checkAll = actions->actionCheckAndAllParents( menu );
314       if ( checkAll )
315         menu->addAction( checkAll );
316 
317       if ( mView->selectedNodes( true ).count() >= 2 )
318         menu->addAction( actions->actionGroupSelected( menu ) );
319 
320       menu->addSeparator();
321 
322       if ( vlayer )
323       {
324         QAction *toggleEditingAction = QgisApp::instance()->actionToggleEditing();
325         QAction *saveLayerEditsAction = QgisApp::instance()->actionSaveActiveLayerEdits();
326         QAction *allEditsAction = QgisApp::instance()->actionAllEdits();
327 
328         // attribute table
329         QgsSettings settings;
330         const QgsAttributeTableFilterModel::FilterMode initialMode = settings.enumValue( QStringLiteral( "qgis/attributeTableBehavior" ),  QgsAttributeTableFilterModel::ShowAll );
331         const auto lambdaOpenAttributeTable = [ = ] { QgisApp::instance()->attributeTable( initialMode ); };
332         QAction *attributeTableAction = menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionOpenTable.svg" ) ), tr( "Open &Attribute Table" ),
333                                         QgisApp::instance(), lambdaOpenAttributeTable );
334         attributeTableAction->setEnabled( vlayer->isValid() );
335 
336         // allow editing
337         const QgsVectorDataProvider *provider = vlayer->dataProvider();
338         if ( vlayer->supportsEditing() )
339         {
340           if ( toggleEditingAction )
341           {
342             menu->addAction( toggleEditingAction );
343             toggleEditingAction->setChecked( vlayer->isEditable() );
344             toggleEditingAction->setEnabled( vlayer->isValid() );
345           }
346           if ( saveLayerEditsAction && vlayer->isModified() )
347           {
348             menu->addAction( saveLayerEditsAction );
349           }
350         }
351 
352         if ( allEditsAction->isEnabled() )
353           menu->addAction( allEditsAction );
354 
355         if ( provider && provider->supportsSubsetString() )
356         {
357           QAction *action = menu->addAction( tr( "&Filter…" ), QgisApp::instance(), qOverload<>( &QgisApp::layerSubsetString ) );
358           action->setEnabled( !vlayer->isEditable() );
359         }
360       }
361 
362       if ( rlayer &&
363            rlayer->dataProvider() &&
364            rlayer->dataProvider()->supportsSubsetString() )
365       {
366         menu->addAction( tr( "&Filter…" ), QgisApp::instance(), qOverload<>( &QgisApp::layerSubsetString ) );
367       }
368 
369       // change data source is only supported for vectors, rasters, point clouds, mesh
370       if ( vlayer || rlayer || pcLayer || meshLayer )
371       {
372 
373         QAction *a = new QAction( layer->isValid() ? tr( "C&hange Data Source…" ) : tr( "Repair Data Source…" ), menu );
374         if ( !layer->isValid() )
375           a->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mIconWarning.svg" ) ) );
376         // Disable when layer is editable
377         if ( layer->isEditable() )
378         {
379           a->setEnabled( false );
380         }
381         else
382         {
383           connect( a, &QAction::triggered, this, [ = ]
384           {
385             QgisApp::instance()->changeDataSource( layer );
386           } );
387         }
388         menu->addAction( a );
389       }
390 
391       // actions on the selection
392       if ( vlayer && vlayer->selectedFeatureCount() > 0 )
393       {
394         const int selectionCount = vlayer->selectedFeatureCount();
395         QgsMapLayerAction::Target target;
396         if ( selectionCount == 1 )
397           target = QgsMapLayerAction::Target::SingleFeature;
398         else
399           target = QgsMapLayerAction::Target::MultipleFeatures;
400 
401         const QList<QgsMapLayerAction *> constRegisteredActions = QgsGui::mapLayerActionRegistry()->mapLayerActions( vlayer, target );
402         if ( !constRegisteredActions.isEmpty() )
403         {
404           QMenu *actionMenu = menu->addMenu( tr( "Actions on Selection (%1)" ).arg( selectionCount ) );
405           for ( QgsMapLayerAction *action : constRegisteredActions )
406           {
407             if ( target == QgsMapLayerAction::Target::SingleFeature )
408             {
409               actionMenu->addAction( action->text(), action, [ = ]() { action->triggerForFeature( vlayer,  vlayer->selectedFeatures().at( 0 ) ); } );
410             }
411             else if ( target == QgsMapLayerAction::Target::MultipleFeatures )
412             {
413               actionMenu->addAction( action->text(), action, [ = ]() {action->triggerForFeatures( vlayer, vlayer->selectedFeatures() );} );
414             }
415           }
416         }
417       }
418 
419       menu->addSeparator();
420 
421       if ( layer && layer->isSpatial() )
422       {
423         // set layer scale visibility
424         menu->addAction( tr( "Set Layer Scale &Visibility…" ), QgisApp::instance(), &QgisApp::setLayerScaleVisibility );
425 
426         if ( !layer->isInScaleRange( mCanvas->scale() ) )
427           menu->addAction( tr( "Zoom to &Visible Scale" ), QgisApp::instance(), &QgisApp::zoomToLayerScale );
428 
429         QMenu *menuSetCRS = new QMenu( tr( "Layer CRS" ), menu );
430 
431         const QList<QgsLayerTreeNode *> selectedNodes = mView->selectedNodes();
432         QgsCoordinateReferenceSystem layerCrs;
433         bool firstLayer = true;
434         bool allSameCrs = true;
435         for ( QgsLayerTreeNode *node : selectedNodes )
436         {
437           if ( QgsLayerTree::isLayer( node ) )
438           {
439             QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
440             if ( nodeLayer->layer() )
441             {
442               if ( firstLayer )
443               {
444                 layerCrs = nodeLayer->layer()->crs();
445                 firstLayer = false;
446               }
447               else if ( nodeLayer->layer()->crs() != layerCrs )
448               {
449                 allSameCrs = false;
450                 break;
451               }
452             }
453           }
454         }
455 
456         QAction *actionCurrentCrs = new QAction( !allSameCrs ? tr( "Mixed CRS" )
457             : layer->crs().isValid() ? layer->crs().userFriendlyIdentifier()
458             : tr( "No CRS" ), menuSetCRS );
459         actionCurrentCrs->setEnabled( false );
460         menuSetCRS->addAction( actionCurrentCrs );
461 
462         if ( allSameCrs && layerCrs.isValid() )
463         {
464           // assign layer crs to project
465           QAction *actionSetProjectCrs = new QAction( tr( "Set &Project CRS from Layer" ), menuSetCRS );
466           connect( actionSetProjectCrs, &QAction::triggered, QgisApp::instance(), &QgisApp::setProjectCrsFromLayer );
467           menuSetCRS->addAction( actionSetProjectCrs );
468         }
469 
470         const QList< QgsCoordinateReferenceSystem> recentProjections = QgsCoordinateReferenceSystem::recentCoordinateReferenceSystems();
471         if ( !recentProjections.isEmpty() )
472         {
473           menuSetCRS->addSeparator();
474           int i = 0;
475           for ( const QgsCoordinateReferenceSystem &crs : recentProjections )
476           {
477             if ( crs == layer->crs() )
478               continue;
479 
480             QAction *action = menuSetCRS->addAction( tr( "Set to %1" ).arg( crs.userFriendlyIdentifier( QgsCoordinateReferenceSystem::ShortString ) ) );
481             connect( action, &QAction::triggered, this, [ = ]
482             {
483               setLayerCrs( crs );
484             } );
485 
486             i++;
487             if ( i == 5 )
488               break;
489           }
490         }
491 
492         // set layer crs
493         menuSetCRS->addSeparator();
494         QAction *actionSetLayerCrs = new QAction( tr( "Set &Layer CRS…" ), menuSetCRS );
495         connect( actionSetLayerCrs, &QAction::triggered, QgisApp::instance(), &QgisApp::setLayerCrs );
496         menuSetCRS->addAction( actionSetLayerCrs );
497 
498         menu->addMenu( menuSetCRS );
499       }
500 
501       menu->addSeparator();
502 
503       // export menu
504       if ( layer )
505       {
506         switch ( layer->type() )
507         {
508           case QgsMapLayerType::VectorLayer:
509             if ( vlayer )
510             {
511               if ( vlayer->isTemporary() )
512               {
513                 QAction *actionMakePermanent = new QAction( QgsApplication::getThemeIcon( QStringLiteral( "mActionFileSave.svg" ) ), tr( "Make Permanent…" ), menu );
514                 connect( actionMakePermanent, &QAction::triggered, QgisApp::instance(), [ = ] { QgisApp::instance()->makeMemoryLayerPermanent( vlayer ); } );
515                 menu->addAction( actionMakePermanent );
516               }
517               // save as vector file
518               QMenu *menuExportVector = new QMenu( tr( "E&xport" ), menu );
519               QAction *actionSaveAs = new QAction( tr( "Save Features &As…" ), menuExportVector );
520               connect( actionSaveAs, &QAction::triggered, QgisApp::instance(), [ = ] { QgisApp::instance()->saveAsFile(); } );
521               actionSaveAs->setEnabled( vlayer->isValid() );
522               menuExportVector->addAction( actionSaveAs );
523               QAction *actionSaveSelectedFeaturesAs = new QAction( tr( "Save &Selected Features As…" ), menuExportVector );
524               connect( actionSaveSelectedFeaturesAs, &QAction::triggered, QgisApp::instance(), [ = ] { QgisApp::instance()->saveAsFile( nullptr, true ); } );
525               actionSaveSelectedFeaturesAs->setEnabled( vlayer->isValid() && vlayer->selectedFeatureCount() > 0 );
526               menuExportVector->addAction( actionSaveSelectedFeaturesAs );
527               QAction *actionSaveAsDefinitionLayer = new QAction( tr( "Save as Layer &Definition File…" ), menuExportVector );
528               connect( actionSaveAsDefinitionLayer, &QAction::triggered, QgisApp::instance(), &QgisApp::saveAsLayerDefinition );
529               menuExportVector->addAction( actionSaveAsDefinitionLayer );
530               if ( vlayer->isSpatial() )
531               {
532                 QAction *actionSaveStyle = new QAction( tr( "Save as &QGIS Layer Style File…" ), menuExportVector );
533                 connect( actionSaveStyle, &QAction::triggered, QgisApp::instance(), [ = ] { QgisApp::instance()->saveStyleFile(); } );
534                 menuExportVector->addAction( actionSaveStyle );
535               }
536               menu->addMenu( menuExportVector );
537             }
538             break;
539 
540           case QgsMapLayerType::RasterLayer:
541             if ( rlayer )
542             {
543               QMenu *menuExportRaster = new QMenu( tr( "E&xport" ), menu );
544               QAction *actionSaveAs = new QAction( tr( "Save &As…" ), menuExportRaster );
545               QAction *actionSaveAsDefinitionLayer = new QAction( tr( "Save as Layer &Definition File…" ), menuExportRaster );
546               QAction *actionSaveStyle = new QAction( tr( "Save as &QGIS Layer Style File…" ), menuExportRaster );
547               connect( actionSaveAs, &QAction::triggered, QgisApp::instance(), [ = ] { QgisApp::instance()->saveAsFile(); } );
548               menuExportRaster->addAction( actionSaveAs );
549               actionSaveAs->setEnabled( rlayer->isValid() );
550               connect( actionSaveAsDefinitionLayer, &QAction::triggered, QgisApp::instance(), &QgisApp::saveAsLayerDefinition );
551               menuExportRaster->addAction( actionSaveAsDefinitionLayer );
552               connect( actionSaveStyle, &QAction::triggered, QgisApp::instance(), [ = ] { QgisApp::instance()->saveStyleFile(); } );
553               menuExportRaster->addAction( actionSaveStyle );
554               menu->addMenu( menuExportRaster );
555             }
556             break;
557 
558           case QgsMapLayerType::MeshLayer:
559           case QgsMapLayerType::VectorTileLayer:
560           case QgsMapLayerType::PointCloudLayer:
561           {
562             QMenu *menuExportRaster = new QMenu( tr( "E&xport" ), menu );
563             QAction *actionSaveAsDefinitionLayer = new QAction( tr( "Save as Layer &Definition File…" ), menuExportRaster );
564             QAction *actionSaveStyle = new QAction( tr( "Save as &QGIS Layer Style File…" ), menuExportRaster );
565             connect( actionSaveAsDefinitionLayer, &QAction::triggered, QgisApp::instance(), &QgisApp::saveAsLayerDefinition );
566             menuExportRaster->addAction( actionSaveAsDefinitionLayer );
567             connect( actionSaveStyle, &QAction::triggered, QgisApp::instance(), [ = ] { QgisApp::instance()->saveStyleFile(); } );
568             menuExportRaster->addAction( actionSaveStyle );
569             menu->addMenu( menuExportRaster );
570           }
571           break;
572 
573           case QgsMapLayerType::AnnotationLayer:
574             break;
575 
576           case QgsMapLayerType::PluginLayer:
577             if ( mView->selectedLayerNodes().count() == 1 )
578             {
579               // disable duplication of plugin layers
580               duplicateLayersAction->setEnabled( false );
581             }
582             break;
583 
584         }
585         menu->addSeparator();
586       }
587 
588       // style-related actions
589       if ( layer && mView->selectedLayerNodes().count() == 1 )
590       {
591         menu->addSeparator();
592         QMenu *menuStyleManager = new QMenu( tr( "Styles" ), menu );
593 
594         QgisApp *app = QgisApp::instance();
595         if ( layer->type() == QgsMapLayerType::VectorLayer )
596         {
597           QMenu *copyStyleMenu = menuStyleManager->addMenu( tr( "Copy Style" ) );
598           copyStyleMenu->setToolTipsVisible( true );
599           QgsMapLayerStyleCategoriesModel *model = new QgsMapLayerStyleCategoriesModel( layer->type(), copyStyleMenu );
600           model->setShowAllCategories( true );
601           for ( int row = 0; row < model->rowCount(); ++row )
602           {
603             const QModelIndex index = model->index( row, 0 );
604             const QgsMapLayer::StyleCategory category = model->data( index, Qt::UserRole ).value<QgsMapLayer::StyleCategory>();
605             const QString name = model->data( index, Qt::DisplayRole ).toString();
606             const QString tooltip = model->data( index, Qt::ToolTipRole ).toString();
607             const QIcon icon = model->data( index, Qt::DecorationRole ).value<QIcon>();
608             QAction *copyAction = new QAction( icon, name, copyStyleMenu );
609             copyAction->setToolTip( tooltip );
610             connect( copyAction, &QAction::triggered, this, [ = ]() {app->copyStyle( layer, category );} );
611             copyStyleMenu->addAction( copyAction );
612             if ( category == QgsMapLayer::AllStyleCategories )
613               copyStyleMenu->addSeparator();
614           }
615         }
616         else
617         {
618           menuStyleManager->addAction( tr( "Copy Style" ), app, [ = ] { app->copyStyle(); } );
619         }
620 
621         if ( layer && app->clipboard()->hasFormat( QGSCLIPBOARD_STYLE_MIME ) )
622         {
623           if ( layer->type() == QgsMapLayerType::VectorLayer )
624           {
625             QDomDocument doc( QStringLiteral( "qgis" ) );
626             QString errorMsg;
627             int errorLine, errorColumn;
628             if ( doc.setContent( app->clipboard()->data( QGSCLIPBOARD_STYLE_MIME ), false, &errorMsg, &errorLine, &errorColumn ) )
629             {
630               const QDomElement myRoot = doc.firstChildElement( QStringLiteral( "qgis" ) );
631               if ( !myRoot.isNull() )
632               {
633                 QMenu *pasteStyleMenu = menuStyleManager->addMenu( tr( "Paste Style" ) );
634                 pasteStyleMenu->setToolTipsVisible( true );
635 
636                 const QgsMapLayer::StyleCategories sourceCategories = QgsXmlUtils::readFlagAttribute( myRoot, QStringLiteral( "styleCategories" ), QgsMapLayer::AllStyleCategories );
637 
638                 QgsMapLayerStyleCategoriesModel *model = new QgsMapLayerStyleCategoriesModel( layer->type(), pasteStyleMenu );
639                 model->setShowAllCategories( true );
640                 for ( int row = 0; row < model->rowCount(); ++row )
641                 {
642                   const QModelIndex index = model->index( row, 0 );
643                   const QgsMapLayer::StyleCategory category = model->data( index, Qt::UserRole ).value<QgsMapLayer::StyleCategory>();
644                   const QString name = model->data( index, Qt::DisplayRole ).toString();
645                   const QString tooltip = model->data( index, Qt::ToolTipRole ).toString();
646                   const QIcon icon = model->data( index, Qt::DecorationRole ).value<QIcon>();
647                   QAction *pasteAction = new QAction( icon, name, pasteStyleMenu );
648                   pasteAction->setToolTip( tooltip );
649                   connect( pasteAction, &QAction::triggered, this, [ = ]() {app->pasteStyle( layer, category );} );
650                   pasteStyleMenu->addAction( pasteAction );
651                   if ( category == QgsMapLayer::AllStyleCategories )
652                     pasteStyleMenu->addSeparator();
653                   else
654                     pasteAction->setEnabled( sourceCategories.testFlag( category ) );
655                 }
656               }
657             }
658           }
659           else
660           {
661             menuStyleManager->addAction( tr( "Paste Style" ), app, [ = ] { app->pasteStyle(); } );
662           }
663         }
664 
665         menuStyleManager->addSeparator();
666         QgsMapLayerStyleGuiUtils::instance()->addStyleManagerActions( menuStyleManager, layer );
667 
668         if ( vlayer )
669         {
670           const QgsSingleSymbolRenderer *singleRenderer = dynamic_cast< const QgsSingleSymbolRenderer * >( vlayer->renderer() );
671           if ( !singleRenderer && vlayer->renderer() && vlayer->renderer()->embeddedRenderer() )
672           {
673             singleRenderer = dynamic_cast< const QgsSingleSymbolRenderer * >( vlayer->renderer()->embeddedRenderer() );
674           }
675           if ( singleRenderer && singleRenderer->symbol() )
676           {
677             //single symbol renderer, so add set color/edit symbol actions
678             menuStyleManager->addSeparator();
679             QgsColorWheel *colorWheel = new QgsColorWheel( menuStyleManager );
680             colorWheel->setColor( singleRenderer->symbol()->color() );
681             QgsColorWidgetAction *colorAction = new QgsColorWidgetAction( colorWheel, menuStyleManager, menuStyleManager );
682             colorAction->setDismissOnColorSelection( false );
683             connect( colorAction, &QgsColorWidgetAction::colorChanged, this, &QgsAppLayerTreeViewMenuProvider::setVectorSymbolColor );
684             //store the layer id in action, so we can later retrieve the corresponding layer
685             colorAction->setProperty( "layerId", vlayer->id() );
686             menuStyleManager->addAction( colorAction );
687 
688             //add recent colors action
689             QList<QgsRecentColorScheme *> recentSchemes;
690             QgsApplication::colorSchemeRegistry()->schemes( recentSchemes );
691             if ( !recentSchemes.isEmpty() )
692             {
693               QgsColorSwatchGridAction *recentColorAction = new QgsColorSwatchGridAction( recentSchemes.at( 0 ), menuStyleManager, QStringLiteral( "symbology" ), menuStyleManager );
694               recentColorAction->setProperty( "layerId", vlayer->id() );
695               recentColorAction->setDismissOnColorSelection( false );
696               menuStyleManager->addAction( recentColorAction );
697               connect( recentColorAction, &QgsColorSwatchGridAction::colorChanged, this, &QgsAppLayerTreeViewMenuProvider::setVectorSymbolColor );
698             }
699 
700             menuStyleManager->addSeparator();
701             const QString layerId = vlayer->id();
702 
703             QAction *editSymbolAction = new QAction( tr( "Edit Symbol…" ), menuStyleManager );
704             connect( editSymbolAction, &QAction::triggered, this, [this, layerId]
705             {
706               editVectorSymbol( layerId );
707             } );
708             menuStyleManager->addAction( editSymbolAction );
709 
710             QAction *copySymbolAction = new QAction( tr( "Copy Symbol" ), menuStyleManager );
711             connect( copySymbolAction, &QAction::triggered, this, [this, layerId]
712             {
713               copyVectorSymbol( layerId );
714             } );
715             menuStyleManager->addAction( copySymbolAction );
716 
717             bool enablePaste = false;
718             const std::unique_ptr< QgsSymbol > tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
719             if ( tempSymbol )
720               enablePaste = true;
721 
722             QAction *pasteSymbolAction = new QAction( tr( "Paste Symbol" ), menuStyleManager );
723             connect( pasteSymbolAction, &QAction::triggered, this, [this, layerId]
724             {
725               pasteVectorSymbol( layerId );
726             } );
727             pasteSymbolAction->setEnabled( enablePaste );
728             menuStyleManager->addAction( pasteSymbolAction );
729           }
730         }
731 
732         menu->addMenu( menuStyleManager );
733       }
734       else
735       {
736         if ( QgisApp::instance()->clipboard()->hasFormat( QGSCLIPBOARD_STYLE_MIME ) )
737         {
738           menu->addAction( tr( "Paste Style" ), QgisApp::instance(), &QgisApp::applyStyleToGroup );
739         }
740       }
741 
742       QAction *notes = new QAction( QgsLayerNotesUtils::layerHasNotes( layer ) ? tr( "Edit Layer Notes…" ) : tr( "Add Layer Notes…" ), menu );
743       connect( notes, &QAction::triggered, this, [layer ]
744       {
745         QgsLayerNotesManager::editLayerNotes( layer, QgisApp::instance() );
746       } );
747       menu->addAction( notes );
748       if ( QgsLayerNotesUtils::layerHasNotes( layer ) )
749       {
750         QAction *notes = new QAction( tr( "Remove Layer Notes" ), menu );
751         connect( notes, &QAction::triggered, this, [layer ]
752         {
753           if ( QMessageBox::question( QgisApp::instance(),
754                                       tr( "Remove Layer Notes" ),
755                                       tr( "Are you sure you want to remove all notes for the layer “%1”?" ).arg( layer->name() ),
756                                       QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) == QMessageBox::Yes )
757           {
758             QgsLayerNotesUtils::removeNotes( layer );
759             QgsProject::instance()->setDirty( true );
760           }
761         } );
762         menu->addAction( notes );
763       }
764 
765       if ( layer && QgsProject::instance()->layerIsEmbedded( layer->id() ).isEmpty() )
766         menu->addAction( tr( "&Properties…" ), QgisApp::instance(), &QgisApp::layerProperties );
767     }
768   }
769   else if ( QgsLayerTreeModelLegendNode *node = mView->index2legendNode( idx ) )
770   {
771     if ( node->flags() & Qt::ItemIsUserCheckable )
772     {
773       menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleAllLayers.svg" ) ), tr( "&Toggle Items" ),
774                        node, &QgsLayerTreeModelLegendNode::toggleAllItems );
775       menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionShowAllLayers.svg" ) ), tr( "&Show All Items" ),
776                        node, &QgsLayerTreeModelLegendNode::checkAllItems );
777       menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionHideAllLayers.svg" ) ), tr( "&Hide All Items" ),
778                        node, &QgsLayerTreeModelLegendNode::uncheckAllItems );
779       menu->addSeparator();
780     }
781 
782     if ( QgsSymbolLegendNode *symbolNode = qobject_cast< QgsSymbolLegendNode * >( node ) )
783     {
784       // symbology item
785       QgsMapLayer *layer = QgsLayerTree::toLayer( node->layerNode() )->layer();
786       if ( layer && layer->type() == QgsMapLayerType::VectorLayer && symbolNode->symbol() )
787       {
788         QgsColorWheel *colorWheel = new QgsColorWheel( menu );
789         colorWheel->setColor( symbolNode->symbol()->color() );
790         QgsColorWidgetAction *colorAction = new QgsColorWidgetAction( colorWheel, menu, menu );
791         colorAction->setDismissOnColorSelection( false );
792         connect( colorAction, &QgsColorWidgetAction::colorChanged, this, &QgsAppLayerTreeViewMenuProvider::setSymbolLegendNodeColor );
793         //store the layer id and rule key in action, so we can later retrieve the corresponding
794         //legend node, if it still exists
795         colorAction->setProperty( "layerId", symbolNode->layerNode()->layerId() );
796         colorAction->setProperty( "ruleKey", symbolNode->data( QgsLayerTreeModelLegendNode::RuleKeyRole ).toString() );
797         menu->addAction( colorAction );
798 
799         //add recent colors action
800         QList<QgsRecentColorScheme *> recentSchemes;
801         QgsApplication::colorSchemeRegistry()->schemes( recentSchemes );
802         if ( !recentSchemes.isEmpty() )
803         {
804           QgsColorSwatchGridAction *recentColorAction = new QgsColorSwatchGridAction( recentSchemes.at( 0 ), menu, QStringLiteral( "symbology" ), menu );
805           recentColorAction->setProperty( "layerId", symbolNode->layerNode()->layerId() );
806           recentColorAction->setProperty( "ruleKey", symbolNode->data( QgsLayerTreeModelLegendNode::RuleKeyRole ).toString() );
807           recentColorAction->setDismissOnColorSelection( false );
808           menu->addAction( recentColorAction );
809           connect( recentColorAction, &QgsColorSwatchGridAction::colorChanged, this, &QgsAppLayerTreeViewMenuProvider::setSymbolLegendNodeColor );
810         }
811 
812         menu->addSeparator();
813       }
814 
815       const QString layerId = symbolNode->layerNode()->layerId();
816       const QString ruleKey = symbolNode->data( QgsLayerTreeModelLegendNode::RuleKeyRole ).toString();
817 
818       if ( layer && layer->type() == QgsMapLayerType::VectorLayer )
819       {
820         QAction *editSymbolAction = new QAction( tr( "Edit Symbol…" ), menu );
821         connect( editSymbolAction, &QAction::triggered, this, [this, layerId, ruleKey ]
822         {
823           editSymbolLegendNodeSymbol( layerId, ruleKey );
824         } );
825         menu->addAction( editSymbolAction );
826       }
827 
828       QAction *copySymbolAction = new QAction( tr( "Copy Symbol" ), menu );
829       connect( copySymbolAction, &QAction::triggered, this, [this, layerId, ruleKey ]
830       {
831         copySymbolLegendNodeSymbol( layerId, ruleKey );
832       } );
833       menu->addAction( copySymbolAction );
834 
835       if ( layer && layer->type() == QgsMapLayerType::VectorLayer )
836       {
837         bool enablePaste = false;
838         const std::unique_ptr< QgsSymbol > tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
839         if ( tempSymbol )
840           enablePaste = true;
841 
842         QAction *pasteSymbolAction = new QAction( tr( "Paste Symbol" ), menu );
843         connect( pasteSymbolAction, &QAction::triggered, this, [this, layerId, ruleKey]
844         {
845           pasteSymbolLegendNodeSymbol( layerId, ruleKey );
846         } );
847         pasteSymbolAction->setEnabled( enablePaste );
848         menu->addAction( pasteSymbolAction );
849       }
850     }
851   }
852 
853   return menu;
854 }
855 
addLegendLayerAction(QAction * action,const QString & menu,QgsMapLayerType type,bool allLayers)856 void QgsAppLayerTreeViewMenuProvider::addLegendLayerAction( QAction *action, const QString &menu,
857     QgsMapLayerType type, bool allLayers )
858 {
859   mLegendLayerActionMap[type].append( LegendLayerAction( action, menu, allLayers ) );
860 }
861 
removeLegendLayerAction(QAction * action)862 bool QgsAppLayerTreeViewMenuProvider::removeLegendLayerAction( QAction *action )
863 {
864   QMap< QgsMapLayerType, QList< LegendLayerAction > >::iterator it;
865   for ( it = mLegendLayerActionMap.begin();
866         it != mLegendLayerActionMap.end(); ++it )
867   {
868     for ( int i = 0; i < it->count(); i++ )
869     {
870       if ( ( *it )[i].action == action )
871       {
872         ( *it ).removeAt( i );
873         return true;
874       }
875     }
876   }
877   return false;
878 }
879 
addLegendLayerActionForLayer(QAction * action,QgsMapLayer * layer)880 void QgsAppLayerTreeViewMenuProvider::addLegendLayerActionForLayer( QAction *action, QgsMapLayer *layer )
881 {
882   if ( !action || !layer )
883     return;
884 
885   legendLayerActions( layer->type() );
886   if ( !mLegendLayerActionMap.contains( layer->type() ) )
887     return;
888 
889   const QMap< QgsMapLayerType, QList< LegendLayerAction > >::iterator it
890     = mLegendLayerActionMap.find( layer->type() );
891   for ( int i = 0; i < it->count(); i++ )
892   {
893     if ( ( *it )[i].action == action )
894     {
895       ( *it )[i].layers.append( layer );
896       return;
897     }
898   }
899 }
900 
removeLegendLayerActionsForLayer(QgsMapLayer * layer)901 void QgsAppLayerTreeViewMenuProvider::removeLegendLayerActionsForLayer( QgsMapLayer *layer )
902 {
903   if ( ! layer || ! mLegendLayerActionMap.contains( layer->type() ) )
904     return;
905 
906   const QMap< QgsMapLayerType, QList< LegendLayerAction > >::iterator it
907     = mLegendLayerActionMap.find( layer->type() );
908   for ( int i = 0; i < it->count(); i++ )
909   {
910     ( *it )[i].layers.removeAll( layer );
911   }
912 }
913 
legendLayerActions(QgsMapLayerType type) const914 QList< LegendLayerAction > QgsAppLayerTreeViewMenuProvider::legendLayerActions( QgsMapLayerType type ) const
915 {
916 #ifdef QGISDEBUG
917   if ( mLegendLayerActionMap.contains( type ) )
918   {
919     QgsDebugMsgLevel( QStringLiteral( "legendLayerActions for layers of type %1:" ).arg( static_cast<int>( type ) ), 2 );
920 
921     const auto legendLayerActions { mLegendLayerActionMap.value( type ) };
922     for ( const LegendLayerAction &lyrAction : legendLayerActions )
923     {
924       Q_UNUSED( lyrAction )
925       QgsDebugMsgLevel( QStringLiteral( "%1/%2 - %3 layers" ).arg( lyrAction.menu, lyrAction.action->text() ).arg( lyrAction.layers.count() ), 2 );
926     }
927   }
928 #endif
929 
930   return mLegendLayerActionMap.contains( type ) ? mLegendLayerActionMap.value( type ) : QList< LegendLayerAction >();
931 }
932 
addCustomLayerActions(QMenu * menu,QgsMapLayer * layer)933 void QgsAppLayerTreeViewMenuProvider::addCustomLayerActions( QMenu *menu, QgsMapLayer *layer )
934 {
935   if ( !layer )
936     return;
937 
938   // add custom layer actions - should this go at end?
939   QList< LegendLayerAction > lyrActions = legendLayerActions( layer->type() );
940 
941   if ( ! lyrActions.isEmpty() )
942   {
943     menu->addSeparator();
944     QList<QMenu *> menus;
945     for ( int i = 0; i < lyrActions.count(); i++ )
946     {
947       if ( lyrActions[i].allLayers || lyrActions[i].layers.contains( layer ) )
948       {
949         if ( lyrActions[i].menu.isEmpty() )
950         {
951           menu->addAction( lyrActions[i].action );
952         }
953         else
954         {
955           // find or create menu for given menu name
956           // adapted from QgisApp::getPluginMenu( QString menuName )
957           QString menuName = lyrActions[i].menu;
958 #ifdef Q_OS_MAC
959           // Mac doesn't have '&' keyboard shortcuts.
960           menuName.remove( QChar( '&' ) );
961 #endif
962           QAction *before = nullptr;
963           QMenu *newMenu = nullptr;
964           QString dst = menuName;
965           dst.remove( QChar( '&' ) );
966           const auto constMenus = menus;
967           for ( QMenu *menu : constMenus )
968           {
969             QString src = menu->title();
970             src.remove( QChar( '&' ) );
971             const int comp = dst.localeAwareCompare( src );
972             if ( comp < 0 )
973             {
974               // Add item before this one
975               before = menu->menuAction();
976               break;
977             }
978             else if ( comp == 0 )
979             {
980               // Plugin menu item already exists
981               newMenu = menu;
982               break;
983             }
984           }
985           if ( ! newMenu )
986           {
987             // It doesn't exist, so create
988             newMenu = new QMenu( menuName );
989             menus.append( newMenu );
990             // Where to put it? - we worked that out above...
991             menu->insertMenu( before, newMenu );
992           }
993           // QMenu* menu = getMenu( lyrActions[i].menu, &beforeSep, &afterSep, &menu );
994           newMenu->addAction( lyrActions[i].action );
995         }
996       }
997     }
998     menu->addSeparator();
999   }
1000 }
1001 
editVectorSymbol(const QString & layerId)1002 void QgsAppLayerTreeViewMenuProvider::editVectorSymbol( const QString &layerId )
1003 {
1004   QgsVectorLayer *layer = QgsProject::instance()->mapLayer<QgsVectorLayer *>( layerId );
1005   if ( !layer )
1006     return;
1007 
1008   QgsSingleSymbolRenderer *singleRenderer = dynamic_cast< QgsSingleSymbolRenderer * >( layer->renderer() );
1009   if ( !singleRenderer )
1010     return;
1011 
1012   std::unique_ptr< QgsSymbol > symbol( singleRenderer->symbol() ? singleRenderer->symbol()->clone() : nullptr );
1013   QgsSymbolSelectorDialog dlg( symbol.get(), QgsStyle::defaultStyle(), layer, mView->window() );
1014   dlg.setWindowTitle( tr( "Symbol Selector" ) );
1015   QgsSymbolWidgetContext context;
1016   context.setMapCanvas( mCanvas );
1017   context.setMessageBar( QgisApp::instance()->messageBar() );
1018   dlg.setContext( context );
1019   if ( dlg.exec() )
1020   {
1021     singleRenderer->setSymbol( symbol.release() );
1022     layer->triggerRepaint();
1023     mView->refreshLayerSymbology( layer->id() );
1024     layer->emitStyleChanged();
1025     QgsProject::instance()->setDirty( true );
1026   }
1027 }
1028 
copyVectorSymbol(const QString & layerId)1029 void QgsAppLayerTreeViewMenuProvider::copyVectorSymbol( const QString &layerId )
1030 {
1031   QgsVectorLayer *layer = QgsProject::instance()->mapLayer<QgsVectorLayer *>( layerId );
1032   if ( !layer )
1033     return;
1034 
1035   QgsSingleSymbolRenderer *singleRenderer = dynamic_cast< QgsSingleSymbolRenderer * >( layer->renderer() );
1036   if ( !singleRenderer )
1037     return;
1038 
1039   QApplication::clipboard()->setMimeData( QgsSymbolLayerUtils::symbolToMimeData( singleRenderer->symbol() ) );
1040 }
1041 
pasteVectorSymbol(const QString & layerId)1042 void QgsAppLayerTreeViewMenuProvider::pasteVectorSymbol( const QString &layerId )
1043 {
1044   QgsVectorLayer *layer = QgsProject::instance()->mapLayer<QgsVectorLayer *>( layerId );
1045   if ( !layer )
1046     return;
1047 
1048   QgsSingleSymbolRenderer *singleRenderer = dynamic_cast< QgsSingleSymbolRenderer * >( layer->renderer() );
1049   if ( !singleRenderer )
1050     return;
1051 
1052   std::unique_ptr< QgsSymbol > tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
1053   if ( !tempSymbol )
1054     return;
1055 
1056   if ( !singleRenderer->symbol() || singleRenderer->symbol()->type() != tempSymbol->type() )
1057     return;
1058 
1059   singleRenderer->setSymbol( tempSymbol.release() );
1060   layer->triggerRepaint();
1061   layer->emitStyleChanged();
1062   mView->refreshLayerSymbology( layer->id() );
1063   QgsProject::instance()->setDirty( true );
1064 }
1065 
setVectorSymbolColor(const QColor & color)1066 void QgsAppLayerTreeViewMenuProvider::setVectorSymbolColor( const QColor &color )
1067 {
1068   QAction *action = qobject_cast< QAction *>( sender() );
1069   if ( !action )
1070     return;
1071 
1072   const QString layerId = action->property( "layerId" ).toString();
1073   QgsVectorLayer *layer = QgsProject::instance()->mapLayer<QgsVectorLayer *>( layerId );
1074   if ( !layer )
1075     return;
1076 
1077   QgsSingleSymbolRenderer *singleRenderer = dynamic_cast< QgsSingleSymbolRenderer * >( layer->renderer() );
1078   QgsSymbol *newSymbol = nullptr;
1079 
1080   if ( singleRenderer && singleRenderer->symbol() )
1081     newSymbol = singleRenderer->symbol()->clone();
1082 
1083   const QgsSingleSymbolRenderer *embeddedRenderer = nullptr;
1084   if ( !newSymbol && layer->renderer()->embeddedRenderer() )
1085   {
1086     embeddedRenderer = dynamic_cast< const QgsSingleSymbolRenderer * >( layer->renderer()->embeddedRenderer() );
1087     if ( embeddedRenderer && embeddedRenderer->symbol() )
1088       newSymbol = embeddedRenderer->symbol()->clone();
1089   }
1090 
1091   if ( !newSymbol )
1092     return;
1093 
1094   newSymbol->setColor( color );
1095   if ( singleRenderer )
1096   {
1097     singleRenderer->setSymbol( newSymbol );
1098   }
1099   else if ( embeddedRenderer )
1100   {
1101     QgsSingleSymbolRenderer *newRenderer = embeddedRenderer->clone();
1102     newRenderer->setSymbol( newSymbol );
1103     layer->renderer()->setEmbeddedRenderer( newRenderer );
1104   }
1105 
1106   layer->triggerRepaint();
1107   layer->emitStyleChanged();
1108   mView->refreshLayerSymbology( layer->id() );
1109   QgsProject::instance()->setDirty( true );
1110 }
1111 
editSymbolLegendNodeSymbol(const QString & layerId,const QString & ruleKey)1112 void QgsAppLayerTreeViewMenuProvider::editSymbolLegendNodeSymbol( const QString &layerId, const QString &ruleKey )
1113 {
1114   QgsSymbolLegendNode *node = qobject_cast<QgsSymbolLegendNode *>( mView->layerTreeModel()->findLegendNode( layerId, ruleKey ) );
1115   if ( !node )
1116     return;
1117 
1118   const QgsSymbol *originalSymbol = node->symbol();
1119   if ( !originalSymbol )
1120   {
1121     QgisApp::instance()->messageBar()->pushWarning( tr( "No Symbol" ), tr( "There is no symbol associated with the rule." ) );
1122     return;
1123   }
1124 
1125   std::unique_ptr< QgsSymbol > symbol( originalSymbol->clone() );
1126   QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( node->layerNode()->layer() );
1127   QgsSymbolSelectorDialog dlg( symbol.get(), QgsStyle::defaultStyle(), vlayer, mView->window() );
1128   dlg.setWindowTitle( tr( "Symbol Selector" ) );
1129   QgsSymbolWidgetContext context;
1130   context.setMapCanvas( mCanvas );
1131   context.setMessageBar( QgisApp::instance()->messageBar() );
1132   dlg.setContext( context );
1133   if ( dlg.exec() )
1134   {
1135     node->setSymbol( symbol.release() );
1136     if ( vlayer )
1137     {
1138       vlayer->emitStyleChanged();
1139     }
1140     QgsProject::instance()->setDirty( true );
1141   }
1142 }
1143 
copySymbolLegendNodeSymbol(const QString & layerId,const QString & ruleKey)1144 void QgsAppLayerTreeViewMenuProvider::copySymbolLegendNodeSymbol( const QString &layerId, const QString &ruleKey )
1145 {
1146   QgsSymbolLegendNode *node = qobject_cast<QgsSymbolLegendNode *>( mView->layerTreeModel()->findLegendNode( layerId, ruleKey ) );
1147   if ( !node )
1148     return;
1149 
1150   const QgsSymbol *originalSymbol = node->symbol();
1151   if ( !originalSymbol )
1152     return;
1153 
1154   QApplication::clipboard()->setMimeData( QgsSymbolLayerUtils::symbolToMimeData( originalSymbol ) );
1155 }
1156 
pasteSymbolLegendNodeSymbol(const QString & layerId,const QString & ruleKey)1157 void QgsAppLayerTreeViewMenuProvider::pasteSymbolLegendNodeSymbol( const QString &layerId, const QString &ruleKey )
1158 {
1159   QgsSymbolLegendNode *node = qobject_cast<QgsSymbolLegendNode *>( mView->layerTreeModel()->findLegendNode( layerId, ruleKey ) );
1160   if ( !node )
1161     return;
1162 
1163   const QgsSymbol *originalSymbol = node->symbol();
1164   if ( !originalSymbol )
1165     return;
1166 
1167   QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( node->layerNode()->layer() );
1168 
1169   std::unique_ptr< QgsSymbol > tempSymbol( QgsSymbolLayerUtils::symbolFromMimeData( QApplication::clipboard()->mimeData() ) );
1170   if ( tempSymbol && tempSymbol->type() == originalSymbol->type() )
1171   {
1172     node->setSymbol( tempSymbol.release() );
1173     if ( vlayer )
1174     {
1175       vlayer->emitStyleChanged();
1176     }
1177     QgsProject::instance()->setDirty( true );
1178   }
1179 }
1180 
setSymbolLegendNodeColor(const QColor & color)1181 void QgsAppLayerTreeViewMenuProvider::setSymbolLegendNodeColor( const QColor &color )
1182 {
1183   QAction *action = qobject_cast< QAction *>( sender() );
1184   if ( !action )
1185     return;
1186 
1187   const QString layerId = action->property( "layerId" ).toString();
1188   const QString ruleKey = action->property( "ruleKey" ).toString();
1189 
1190   QgsSymbolLegendNode *node = qobject_cast<QgsSymbolLegendNode *>( mView->layerTreeModel()->findLegendNode( layerId, ruleKey ) );
1191   if ( !node )
1192     return;
1193 
1194   const QgsSymbol *originalSymbol = node->symbol();
1195   if ( !originalSymbol )
1196     return;
1197 
1198   std::unique_ptr< QgsSymbol > newSymbol( originalSymbol->clone() );
1199   newSymbol->setColor( color );
1200   node->setSymbol( newSymbol.release() );
1201   if ( QgsVectorLayer *layer = QgsProject::instance()->mapLayer<QgsVectorLayer *>( layerId ) )
1202   {
1203     layer->emitStyleChanged();
1204   }
1205   QgsProject::instance()->setDirty( true );
1206 }
1207 
removeActionEnabled()1208 bool QgsAppLayerTreeViewMenuProvider::removeActionEnabled()
1209 {
1210   const QList<QgsLayerTreeLayer *> selectedLayers = mView->selectedLayerNodes();
1211   for ( QgsLayerTreeLayer *nodeLayer : selectedLayers )
1212   {
1213     // be careful with the logic here -- if nodeLayer->layer() is false, will still must return true
1214     // to allow the broken layer to be removed from the project
1215     if ( nodeLayer->layer() && !nodeLayer->layer()->flags().testFlag( QgsMapLayer::Removable ) )
1216       return false;
1217   }
1218   return true;
1219 }
1220 
setLayerCrs(const QgsCoordinateReferenceSystem & crs)1221 void QgsAppLayerTreeViewMenuProvider::setLayerCrs( const QgsCoordinateReferenceSystem &crs )
1222 {
1223   const auto constSelectedNodes = mView->selectedNodes();
1224   for ( QgsLayerTreeNode *node : constSelectedNodes )
1225   {
1226     if ( QgsLayerTree::isLayer( node ) )
1227     {
1228       QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
1229       if ( nodeLayer->layer() )
1230       {
1231         nodeLayer->layer()->setCrs( crs, true );
1232         nodeLayer->layer()->triggerRepaint();
1233       }
1234     }
1235   }
1236 }
1237 
toggleLabels(bool enabled)1238 void QgsAppLayerTreeViewMenuProvider::toggleLabels( bool enabled )
1239 {
1240   const QList<QgsLayerTreeLayer *> selectedLayerNodes = mView->selectedLayerNodes();
1241   for ( QgsLayerTreeLayer *l : selectedLayerNodes )
1242   {
1243     QgsVectorLayer *vlayer = qobject_cast< QgsVectorLayer * >( l->layer() );
1244     if ( !vlayer || !vlayer->isSpatial() )
1245       continue;
1246 
1247     if ( enabled && !vlayer->labeling() )
1248     {
1249       // no labeling setup - create default labeling for layer
1250       const QgsPalLayerSettings settings = QgsAbstractVectorLayerLabeling::defaultSettingsForLayer( vlayer );
1251       vlayer->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );
1252       vlayer->setLabelsEnabled( true );
1253     }
1254     else
1255     {
1256       vlayer->setLabelsEnabled( enabled );
1257     }
1258     vlayer->emitStyleChanged();
1259     vlayer->triggerRepaint();
1260   }
1261 }
1262