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