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