Skip to content
Permalink
Browse files

Merge pull request #6782 from wonder-sk/required-layers

Mark layers as required in the project
  • Loading branch information
wonder-sk committed Apr 13, 2018
2 parents b662b44 + 7e022f0 commit 322bc78755371677d5fa4423e0cfea39ba655514
@@ -952,6 +952,26 @@ Sets the project's ``metadata`` store.
.. seealso:: :py:func:`metadata`

.. seealso:: :py:func:`metadataChanged`
%End

QSet<QgsMapLayer *> requiredLayers() const;
%Docstring
Returns a set of map layers that are required in the project and therefore they should not get
removed from the project. The set of layers may be configured by users in project properties.
and it is mainly a hint for the user interface to protect users from removing layers that important
in the project. The removeMapLayer(), removeMapLayers() calls do not block removal of layers listed here.

.. versionadded:: 3.2
%End

void setRequiredLayers( const QSet<QgsMapLayer *> &layers );
%Docstring
Configures a set of map layers that are required in the project and therefore they should not get
removed from the project. The set of layers may be configured by users in project properties.
and it is mainly a hint for the user interface to protect users from removing layers that important
in the project. The removeMapLayer(), removeMapLayers() calls do not block removal of layers listed here.

.. versionadded:: 3.2
%End

signals:
@@ -9298,15 +9298,17 @@ void QgisApp::removeLayer()
return;
}

Q_FOREACH ( QgsMapLayer *layer, mLayerTreeView->selectedLayers() )
const QList<QgsMapLayer *> selectedLayers = mLayerTreeView->selectedLayers();

for ( QgsMapLayer *layer : selectedLayers )
{
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
if ( vlayer && vlayer->isEditable() && !toggleEditing( vlayer, true ) )
return;
}

QStringList activeTaskDescriptions;
Q_FOREACH ( QgsMapLayer *layer, mLayerTreeView->selectedLayers() )
for ( QgsMapLayer *layer : selectedLayers )
{
QList< QgsTask * > tasks = QgsApplication::taskManager()->tasksDependentOnLayer( layer );
if ( !tasks.isEmpty() )
@@ -9318,6 +9320,16 @@ void QgisApp::removeLayer()
}
}

// extra check for required layers
// In theory it should not be needed because the remove action should be disabled
// if there are required layers in the selection...
const QSet<QgsMapLayer *> requiredLayers = QgsProject::instance()->requiredLayers();
for ( QgsMapLayer *layer : selectedLayers )
{
if ( requiredLayers.contains( layer ) )
return;
}

if ( !activeTaskDescriptions.isEmpty() )
{
QMessageBox::warning( this, tr( "Active Tasks" ),
@@ -11573,7 +11585,7 @@ void QgisApp::selectionChanged( QgsMapLayer *layer )

void QgisApp::legendLayerSelectionChanged()
{
QList<QgsLayerTreeLayer *> selectedLayers = mLayerTreeView ? mLayerTreeView->selectedLayerNodes() : QList<QgsLayerTreeLayer *>();
const QList<QgsLayerTreeLayer *> selectedLayers = mLayerTreeView ? mLayerTreeView->selectedLayerNodes() : QList<QgsLayerTreeLayer *>();

mActionDuplicateLayer->setEnabled( !selectedLayers.isEmpty() );
mActionSetLayerScaleVisibility->setEnabled( !selectedLayers.isEmpty() );
@@ -11599,6 +11611,19 @@ void QgisApp::legendLayerSelectionChanged()
mLegendExpressionFilterButton->setChecked( exprEnabled );
}
}

// remove action - check for required layers
bool removeEnabled = true;
const QSet<QgsMapLayer *> requiredLayers = QgsProject::instance()->requiredLayers();
for ( QgsLayerTreeLayer *nodeLayer : selectedLayers )
{
if ( requiredLayers.contains( nodeLayer->layer() ) )
{
removeEnabled = false;
break;
}
}
mActionRemoveLayer->setEnabled( removeEnabled );
}

void QgisApp::layerEditStateChanged()
@@ -87,7 +87,8 @@ QMenu *QgsAppLayerTreeViewMenuProvider::createContextMenu()

menu->addSeparator();
menu->addAction( actions->actionAddGroup( menu ) );
menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRemoveLayer.svg" ) ), tr( "&Remove Group…" ), QgisApp::instance(), SLOT( removeLayer() ) );
QAction *removeAction = menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRemoveLayer.svg" ) ), tr( "&Remove Group…" ), QgisApp::instance(), SLOT( removeLayer() ) );
removeAction->setEnabled( removeActionEnabled() );
menu->addSeparator();

menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionSetCRS.png" ) ),
@@ -171,7 +172,8 @@ QMenu *QgsAppLayerTreeViewMenuProvider::createContextMenu()

// duplicate layer
QAction *duplicateLayersAction = menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDuplicateLayer.svg" ) ), tr( "&Duplicate Layer" ), QgisApp::instance(), SLOT( duplicateLayers() ) );
menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRemoveLayer.svg" ) ), tr( "&Remove Layer…" ), QgisApp::instance(), SLOT( removeLayer() ) );
QAction *removeAction = menu->addAction( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRemoveLayer.svg" ) ), tr( "&Remove Layer…" ), QgisApp::instance(), SLOT( removeLayer() ) );
removeAction->setEnabled( removeActionEnabled() );

menu->addSeparator();

@@ -708,3 +710,15 @@ void QgsAppLayerTreeViewMenuProvider::setSymbolLegendNodeColor( const QColor &co
layer->emitStyleChanged();
}
}

bool QgsAppLayerTreeViewMenuProvider::removeActionEnabled()
{
const QList<QgsLayerTreeLayer *> selectedLayers = mView->selectedLayerNodes();
const QSet<QgsMapLayer *> requiredLayers = QgsProject::instance()->requiredLayers();
for ( QgsLayerTreeLayer *nodeLayer : selectedLayers )
{
if ( requiredLayers.contains( nodeLayer->layer() ) )
return false;
}
return true;
}
@@ -67,6 +67,9 @@ class QgsAppLayerTreeViewMenuProvider : public QObject, public QgsLayerTreeViewM
void setVectorSymbolColor( const QColor &color );
void editSymbolLegendNodeSymbol();
void setSymbolLegendNodeColor( const QColor &color );

private:
bool removeActionEnabled();
};

#endif // QGSAPPLAYERTREEVIEWMENUPROVIDER_H
@@ -831,6 +831,7 @@ QgsProjectProperties::QgsProjectProperties( QgsMapCanvas *mapCanvas, QWidget *pa
connect( titleEdit, &QLineEdit::textChanged, mMetadataWidget, &QgsMetadataWidget::setTitle );

projectionSelectorInitialized();
populateRequiredLayers();
restoreOptionsBaseUi();
restoreState();
}
@@ -1283,6 +1284,8 @@ void QgsProjectProperties::apply()
canvas->refresh();
}
QgisApp::instance()->mapOverviewCanvas()->refresh();

applyRequiredLayers();
}

void QgsProjectProperties::showProjectionsTab()
@@ -2122,3 +2125,37 @@ void QgsProjectProperties::showHelp()
}
QgsHelp::openHelp( link );
}

void QgsProjectProperties::populateRequiredLayers()
{
const QSet<QgsMapLayer *> requiredLayers = QgsProject::instance()->requiredLayers();
QStandardItemModel *model = new QStandardItemModel( mViewRequiredLayers );
QList<QgsLayerTreeLayer *> layers = QgsProject::instance()->layerTreeRoot()->findLayers();
std::sort( layers.begin(), layers.end(), []( QgsLayerTreeLayer * layer1, QgsLayerTreeLayer * layer2 ) { return layer1->name() < layer2->name(); } );
for ( const QgsLayerTreeLayer *l : layers )
{
QStandardItem *item = new QStandardItem( l->name() );
item->setCheckable( true );
item->setCheckState( requiredLayers.contains( l->layer() ) ? Qt::Checked : Qt::Unchecked );
item->setData( l->layerId() );
model->appendRow( item );
}

mViewRequiredLayers->setModel( model );
}

void QgsProjectProperties::applyRequiredLayers()
{
QSet<QgsMapLayer *> requiredLayers;
QAbstractItemModel *model = mViewRequiredLayers->model();
for ( int i = 0; i < model->rowCount(); ++i )
{
if ( model->data( model->index( i, 0 ), Qt::CheckStateRole ).toInt() == Qt::Checked )
{
QString layerId = model->data( model->index( i, 0 ), Qt::UserRole + 1 ).toString();
if ( QgsMapLayer *layer = QgsProject::instance()->mapLayer( layerId ) )
requiredLayers << layer;
}
}
QgsProject::instance()->setRequiredLayers( requiredLayers );
}
@@ -224,4 +224,7 @@ class APP_EXPORT QgsProjectProperties : public QgsOptionsDialogBase, private Ui:
void updateGuiForMapUnits();

void showHelp();

void populateRequiredLayers();
void applyRequiredLayers();
};
@@ -2660,3 +2660,25 @@ void QgsProject::setMetadata( const QgsProjectMetadata &metadata )

setDirty( true );
}

QSet<QgsMapLayer *> QgsProject::requiredLayers() const
{
QSet<QgsMapLayer *> layers;
const QStringList lst = readListEntry( QStringLiteral( "RequiredLayers" ), QStringLiteral( "Layers" ) );
for ( const QString &layerId : lst )
{
if ( QgsMapLayer *layer = mapLayer( layerId ) )
layers.insert( layer );
}
return layers;
}

void QgsProject::setRequiredLayers( const QSet<QgsMapLayer *> &layers )
{
QStringList layerIds;
for ( QgsMapLayer *layer : layers )
{
layerIds << layer->id();
}
writeEntry( QStringLiteral( "RequiredLayers" ), QStringLiteral( "Layers" ), layerIds );
}
@@ -928,6 +928,24 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera
*/
void setMetadata( const QgsProjectMetadata &metadata );

/**
* Returns a set of map layers that are required in the project and therefore they should not get
* removed from the project. The set of layers may be configured by users in project properties.
* and it is mainly a hint for the user interface to protect users from removing layers that important
* in the project. The removeMapLayer(), removeMapLayers() calls do not block removal of layers listed here.
* \since QGIS 3.2
*/
QSet<QgsMapLayer *> requiredLayers() const;

/**
* Configures a set of map layers that are required in the project and therefore they should not get
* removed from the project. The set of layers may be configured by users in project properties.
* and it is mainly a hint for the user interface to protect users from removing layers that important
* in the project. The removeMapLayer(), removeMapLayers() calls do not block removal of layers listed here.
* \since QGIS 3.2
*/
void setRequiredLayers( const QSet<QgsMapLayer *> &layers );

signals:
//! emitted when project is being read
void readProject( const QDomDocument & );
@@ -1443,7 +1443,7 @@
</property>
</widget>
</item>
<item row="2" column="0">
<item row="3" column="0">
<widget class="QCheckBox" name="mTrustProjectCheckBox">
<property name="toolTip">
<string>Speed up project loading by skipping data checks. Useful in qgis server context or project with huge database views or materialized views.</string>
@@ -1453,6 +1453,28 @@
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QgsCollapsibleGroupBox" name="groupBox_5">
<property name="title">
<string>Required layers</string>
</property>
<layout class="QGridLayout" name="gridLayout_19">
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label_31">
<property name="text">
<string>Checked layers in this list are protected from inadvertent removal from the project.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QListView" name="mViewRequiredLayers"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="mTabRelations">
@@ -40,6 +40,7 @@ class TestQgsProject : public QObject
void testPathResolverSvg();
void testProjectUnits();
void variablesChanged();
void testRequiredLayers();
};

void TestQgsProject::init()
@@ -347,6 +348,40 @@ void TestQgsProject::variablesChanged()
delete prj;
}

void TestQgsProject::testRequiredLayers()
{
QString dataDir( TEST_DATA_DIR ); //defined in CmakeLists.txt
QString layerPath = dataDir + "/points.shp";
QgsVectorLayer *layer1 = new QgsVectorLayer( layerPath, QStringLiteral( "points 1" ), QStringLiteral( "ogr" ) );
QgsVectorLayer *layer2 = new QgsVectorLayer( layerPath, QStringLiteral( "points 2" ), QStringLiteral( "ogr" ) );

QgsProject prj;
prj.addMapLayer( layer1 );
prj.addMapLayer( layer2 );

QSet<QgsMapLayer *> reqLayers;
reqLayers << layer2;
prj.setRequiredLayers( reqLayers );

QSet<QgsMapLayer *> reqLayersReturned = prj.requiredLayers();
QCOMPARE( reqLayersReturned.count(), 1 );
QCOMPARE( *reqLayersReturned.constBegin(), layer2 );

QTemporaryFile f;
QVERIFY( f.open() );
f.close();
prj.setFileName( f.fileName() );
prj.write();

// test reading required layers back
QgsProject prj2;
prj2.setFileName( f.fileName() );
QVERIFY( prj2.read() );
QSet<QgsMapLayer *> reqLayersReturned2 = prj2.requiredLayers();
QCOMPARE( reqLayersReturned2.count(), 1 );
QCOMPARE( ( *reqLayersReturned.constBegin() )->name(), QString( "points 2" ) );
}


QGSTEST_MAIN( TestQgsProject )
#include "testqgsproject.moc"

0 comments on commit 322bc78

Please sign in to comment.
You can’t perform that action at this time.