Skip to content
Permalink
Browse files

[FEATURE] Mark layers as required in the project

Required layers are not allowed to be removed from the project.
This adds extra safety to protect project users from removing layers
they may think are not needed (e.g. used in joins, relations, expressions).

Users can set/unset layers that are required in project properties dialog.
  • Loading branch information
wonder-sk committed Apr 11, 2018
1 parent 3400199 commit 42517f8b58bc61498f3cb74bff4e528da27bea27
@@ -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,18 @@ void QgisApp::removeLayer()
}
}

const QSet<QgsMapLayer *> requiredLayers = QgsProject::instance()->requiredLayers();
for ( QgsMapLayer *layer : selectedLayers )
{
if ( requiredLayers.contains( layer ) )
{
QMessageBox::warning( this, tr( "Required layers" ),
tr( "The layer '%1' is marked as a required and therefore it cannot be removed from the project.\n\n"
"If you really need to remove the layer, unmark it as required in the Project Properties window > Data Sources tab." ).arg( layer->name() ) );
return;
}
}

if ( !activeTaskDescriptions.isEmpty() )
{
QMessageBox::warning( this, tr( "Active Tasks" ),
@@ -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">

0 comments on commit 42517f8

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