diff --git a/images/images.qrc b/images/images.qrc
index 7db93727c21b..e8b7e373989b 100644
--- a/images/images.qrc
+++ b/images/images.qrc
@@ -858,6 +858,7 @@
themes/default/mIconModelInput.svg
themes/default/mIndicatorTemporal.svg
themes/default/mIndicatorTimeFromProject.svg
+ themes/default/mIndicatorOffline.svg
themes/default/temporal_navigation/back.svg
themes/default/temporal_navigation/forward.svg
themes/default/temporal_navigation/next.svg
diff --git a/images/themes/default/mIndicatorOffline.svg b/images/themes/default/mIndicatorOffline.svg
new file mode 100644
index 000000000000..b36064e5e843
--- /dev/null
+++ b/images/themes/default/mIndicatorOffline.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/python/core/auto_generated/qgsmaplayer.sip.in b/python/core/auto_generated/qgsmaplayer.sip.in
index b89622e8ac4c..adcead6062de 100644
--- a/python/core/auto_generated/qgsmaplayer.sip.in
+++ b/python/core/auto_generated/qgsmaplayer.sip.in
@@ -1598,6 +1598,12 @@ Emitted when the validity of this layer changed.
.. versionadded:: 3.16
%End
+ void customPropertyChanged( const QString &key );
+%Docstring
+Emitted when a custom property of the layer has been changed or removed.
+
+.. versionadded:: 3.18
+%End
protected:
diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt
index 76e52fd77ec3..35f21f1086d0 100644
--- a/src/app/CMakeLists.txt
+++ b/src/app/CMakeLists.txt
@@ -52,6 +52,7 @@ SET(QGIS_APP_SRCS
qgslayertreeviewnonremovableindicator.cpp
qgslayertreeviewbadlayerindicator.cpp
qgslayertreeviewtemporalindicator.cpp
+ qgslayertreeviewofflineindicator.cpp
qgsmapcanvasdockwidget.cpp
qgsmapsavedialog.cpp
qgsprojectlistitemdelegate.cpp
diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp
index 63456b2d5b7b..807b1acc7d8c 100644
--- a/src/app/qgisapp.cpp
+++ b/src/app/qgisapp.cpp
@@ -249,6 +249,7 @@ Q_GUI_EXPORT extern int qt_defaultDpiX();
#include "qgslayertreeviewnonremovableindicator.h"
#include "qgslayertreeviewnocrsindicator.h"
#include "qgslayertreeviewtemporalindicator.h"
+#include "qgslayertreeviewofflineindicator.h"
#include "qgslayout.h"
#include "qgslayoutatlas.h"
#include "qgslayoutcustomdrophandler.h"
@@ -4750,6 +4751,7 @@ void QgisApp::initLayerTreeView()
new QgsLayerTreeViewMemoryIndicatorProvider( mLayerTreeView ); // gets parented to the layer view
new QgsLayerTreeViewTemporalIndicatorProvider( mLayerTreeView ); // gets parented to the layer view
new QgsLayerTreeViewNoCrsIndicatorProvider( mLayerTreeView ); // gets parented to the layer view
+ new QgsLayerTreeViewOfflineIndicatorProvider( mLayerTreeView ); // gets parented to the layer view
QgsLayerTreeViewBadLayerIndicatorProvider *badLayerIndicatorProvider = new QgsLayerTreeViewBadLayerIndicatorProvider( mLayerTreeView ); // gets parented to the layer view
connect( badLayerIndicatorProvider, &QgsLayerTreeViewBadLayerIndicatorProvider::requestChangeDataSource, this, &QgisApp::changeDataSource );
new QgsLayerTreeViewNonRemovableIndicatorProvider( mLayerTreeView ); // gets parented to the layer view
diff --git a/src/app/qgslayertreeviewofflineindicator.cpp b/src/app/qgslayertreeviewofflineindicator.cpp
new file mode 100644
index 000000000000..f92874d108a4
--- /dev/null
+++ b/src/app/qgslayertreeviewofflineindicator.cpp
@@ -0,0 +1,68 @@
+/***************************************************************************
+ qgslayertreeviewofflineindicator.cpp
+ --------------------------------------
+ Date : October 2020
+ Copyright : (C) 2020 by David Signer
+ Email : david at opengis dot ch
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+
+#include "qgslayertreeviewofflineindicator.h"
+#include "qgslayertreeview.h"
+#include "qgslayertree.h"
+#include "qgslayertreemodel.h"
+#include "qgisapp.h"
+
+QgsLayerTreeViewOfflineIndicatorProvider::QgsLayerTreeViewOfflineIndicatorProvider( QgsLayerTreeView *view )
+ : QgsLayerTreeViewIndicatorProvider( view )
+{
+
+}
+
+void QgsLayerTreeViewOfflineIndicatorProvider::connectSignals( QgsMapLayer *layer )
+{
+ if ( !layer )
+ return;
+
+ connect( layer, &QgsMapLayer::customPropertyChanged, this, &QgsLayerTreeViewOfflineIndicatorProvider::onLayerChanged );
+}
+
+void QgsLayerTreeViewOfflineIndicatorProvider::disconnectSignals( QgsMapLayer *layer )
+{
+ if ( !layer )
+ return;
+
+ disconnect( layer, &QgsMapLayer::customPropertyChanged, this, &QgsLayerTreeViewOfflineIndicatorProvider::onLayerChanged );
+}
+
+bool QgsLayerTreeViewOfflineIndicatorProvider::acceptLayer( QgsMapLayer *layer )
+{
+ return layer->customProperty( QStringLiteral( "isOfflineEditable" ), false ).toBool();
+}
+
+QString QgsLayerTreeViewOfflineIndicatorProvider::iconName( QgsMapLayer *layer )
+{
+ Q_UNUSED( layer )
+ return QStringLiteral( "/mIndicatorOffline.svg" );
+}
+
+QString QgsLayerTreeViewOfflineIndicatorProvider::tooltipText( QgsMapLayer *layer )
+{
+ Q_UNUSED( layer )
+ return tr( "Offline layer" );
+}
+
+void QgsLayerTreeViewOfflineIndicatorProvider::onLayerChanged()
+{
+ QgsMapLayer *layer = qobject_cast( sender() );
+ if ( !layer )
+ return;
+
+ updateLayerIndicator( layer );
+}
diff --git a/src/app/qgslayertreeviewofflineindicator.h b/src/app/qgslayertreeviewofflineindicator.h
new file mode 100644
index 000000000000..c6e9bdcc3ec9
--- /dev/null
+++ b/src/app/qgslayertreeviewofflineindicator.h
@@ -0,0 +1,47 @@
+/***************************************************************************
+ qgslayertreeviewofflineindicator.h
+ --------------------------------------
+ Date : October 2020
+ Copyright : (C) 2020 by David Signer
+ Email : david at opengis dot ch
+ ***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+
+#ifndef QGSLAYERTREEVIEWOFFLINEINDICATOR_H
+#define QGSLAYERTREEVIEWOFFLINEINDICATOR_H
+
+#include "qgslayertreeviewindicatorprovider.h"
+
+#include
+#include
+
+class QgsLayerTreeNode;
+class QgsLayerTreeView;
+
+//! Adds indicators showing whether layers are offline.
+class QgsLayerTreeViewOfflineIndicatorProvider : public QgsLayerTreeViewIndicatorProvider
+{
+ Q_OBJECT
+ public:
+ explicit QgsLayerTreeViewOfflineIndicatorProvider( QgsLayerTreeView *view );
+
+ protected:
+ void connectSignals( QgsMapLayer *layer ) override;
+ void disconnectSignals( QgsMapLayer *layer ) override;
+
+ protected slots:
+ void onLayerChanged();
+
+ private:
+ bool acceptLayer( QgsMapLayer *layer ) override;
+ QString iconName( QgsMapLayer *layer ) override;
+ QString tooltipText( QgsMapLayer *layer ) override;
+};
+
+#endif // QGSLAYERTREEVIEWOFFLINEINDICATOR_H
diff --git a/src/core/qgsmaplayer.cpp b/src/core/qgsmaplayer.cpp
index c6dddfdd026b..bd5d3b8fa826 100644
--- a/src/core/qgsmaplayer.cpp
+++ b/src/core/qgsmaplayer.cpp
@@ -1707,7 +1707,11 @@ QStringList QgsMapLayer::customPropertyKeys() const
void QgsMapLayer::setCustomProperty( const QString &key, const QVariant &value )
{
- mCustomProperties.setValue( key, value );
+ if ( !mCustomProperties.contains( key ) || mCustomProperties.value( key ) != value )
+ {
+ mCustomProperties.setValue( key, value );
+ emit customPropertyChanged( key );
+ }
}
void QgsMapLayer::setCustomProperties( const QgsObjectCustomProperties &properties )
@@ -1727,7 +1731,12 @@ QVariant QgsMapLayer::customProperty( const QString &value, const QVariant &defa
void QgsMapLayer::removeCustomProperty( const QString &key )
{
- mCustomProperties.remove( key );
+
+ if ( mCustomProperties.contains( key ) )
+ {
+ mCustomProperties.remove( key );
+ emit customPropertyChanged( key );
+ }
}
QgsError QgsMapLayer::error() const
diff --git a/src/core/qgsmaplayer.h b/src/core/qgsmaplayer.h
index 40399273f2f6..02964f912c74 100644
--- a/src/core/qgsmaplayer.h
+++ b/src/core/qgsmaplayer.h
@@ -1429,6 +1429,12 @@ class CORE_EXPORT QgsMapLayer : public QObject
*/
void isValidChanged();
+ /**
+ * Emitted when a custom property of the layer has been changed or removed.
+ *
+ * \since QGIS 3.18
+ */
+ void customPropertyChanged( const QString &key );
private slots:
diff --git a/tests/src/python/test_qgsvectorlayer.py b/tests/src/python/test_qgsvectorlayer.py
index 0f135c159420..a51d8c2ad2e9 100644
--- a/tests/src/python/test_qgsvectorlayer.py
+++ b/tests/src/python/test_qgsvectorlayer.py
@@ -399,6 +399,46 @@ def testSetDataSourceInvalidToValid(self):
# should STILL have kept renderer!
self.assertEqual(layer.renderer(), r)
+ def testSetCustomProperty(self):
+ """
+ Test setting a custom property of the layer
+ """
+ layer = createLayerWithOnePoint()
+ layer.setCustomProperty('Key_0', 'Value_0')
+ layer.setCustomProperty('Key_1', 'Value_1')
+
+ spy = QSignalSpy(layer.customPropertyChanged)
+
+ # change nothing by setting the same value
+ layer.setCustomProperty('Key_0', 'Value_0')
+ layer.setCustomProperty('Key_1', 'Value_1')
+ self.assertEqual(len(spy), 0)
+
+ # change one
+ layer.setCustomProperty('Key_0', 'Value zero')
+ self.assertEqual(len(spy), 1)
+
+ # add one
+ layer.setCustomProperty('Key_2', 'Value two')
+ self.assertEqual(len(spy), 2)
+
+ # add a null one and an empty one
+ layer.setCustomProperty('Key_3', None)
+ layer.setCustomProperty('Key_4', '')
+ self.assertEqual(len(spy), 4)
+
+ # remove one
+ layer.removeCustomProperty('Key_0')
+ self.assertEqual(len(spy), 5)
+
+ self.assertEqual(layer.customProperty('Key_0', 'no value'), 'no value')
+ self.assertEqual(layer.customProperty('Key_1', 'no value'), 'Value_1')
+ self.assertEqual(layer.customProperty('Key_2', 'no value'), 'Value two')
+ self.assertEqual(layer.customProperty('Key_3', 'no value'), None)
+ self.assertEqual(layer.customProperty('Key_4', 'no value'), '')
+
+ self.assertEqual(len(spy), 5)
+
def testStoreWkbTypeInvalidLayers(self):
"""
Test that layer wkb types are restored for projects with invalid layer paths