Skip to content

Commit

Permalink
[FEATURE] Mark layers as required in the project
Browse files Browse the repository at this point in the history
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 42517f8
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 3 deletions.
20 changes: 20 additions & 0 deletions python/core/qgsproject.sip.in
Expand Up @@ -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:
Expand Down
18 changes: 16 additions & 2 deletions src/app/qgisapp.cpp
Expand Up @@ -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() )
Expand All @@ -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" ),
Expand Down
37 changes: 37 additions & 0 deletions src/app/qgsprojectproperties.cpp
Expand Up @@ -831,6 +831,7 @@ QgsProjectProperties::QgsProjectProperties( QgsMapCanvas *mapCanvas, QWidget *pa
connect( titleEdit, &QLineEdit::textChanged, mMetadataWidget, &QgsMetadataWidget::setTitle );

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

applyRequiredLayers();
}

void QgsProjectProperties::showProjectionsTab()
Expand Down Expand Up @@ -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 );
}
3 changes: 3 additions & 0 deletions src/app/qgsprojectproperties.h
Expand Up @@ -224,4 +224,7 @@ class APP_EXPORT QgsProjectProperties : public QgsOptionsDialogBase, private Ui:
void updateGuiForMapUnits();

void showHelp();

void populateRequiredLayers();
void applyRequiredLayers();
};
22 changes: 22 additions & 0 deletions src/core/qgsproject.cpp
Expand Up @@ -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 );
}
18 changes: 18 additions & 0 deletions src/core/qgsproject.h
Expand Up @@ -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 & );
Expand Down
24 changes: 23 additions & 1 deletion src/ui/qgsprojectpropertiesbase.ui
Expand Up @@ -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>
Expand All @@ -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">
Expand Down

0 comments on commit 42517f8

Please sign in to comment.