diff --git a/images/images.qrc b/images/images.qrc
index c6e005f8515..63dcfdfd7c8 100644
--- a/images/images.qrc
+++ b/images/images.qrc
@@ -639,6 +639,7 @@
themes/default/propertyicons/colors.svg
themes/default/propertyicons/system.svg
themes/default/propertyicons/transparency.svg
+ themes/default/propertyicons/sensor.svg
themes/default/propertyicons/spacer.svg
themes/default/propertyicons/relations.svg
themes/default/rendererCategorizedSymbol.svg
diff --git a/images/themes/default/mActionStop.svg b/images/themes/default/mActionStop.svg
index cfc08ec760e..867c8fe3f5d 100644
--- a/images/themes/default/mActionStop.svg
+++ b/images/themes/default/mActionStop.svg
@@ -1 +1,5 @@
-
\ No newline at end of file
+
+
diff --git a/images/themes/default/propertyicons/sensor.svg b/images/themes/default/propertyicons/sensor.svg
new file mode 100644
index 00000000000..e4a9daf94c2
--- /dev/null
+++ b/images/themes/default/propertyicons/sensor.svg
@@ -0,0 +1,11 @@
+
+
diff --git a/python/gui/auto_generated/qgsoptionswidgetfactory.sip.in b/python/gui/auto_generated/qgsoptionswidgetfactory.sip.in
index b20389b3877..0bfae9e838f 100644
--- a/python/gui/auto_generated/qgsoptionswidgetfactory.sip.in
+++ b/python/gui/auto_generated/qgsoptionswidgetfactory.sip.in
@@ -56,6 +56,12 @@ The default implementation returns ``True``.
%Docstring
Called to permanently apply the settings shown in the options page (e.g. save them to
:py:class:`QgsSettings` objects). This is usually called when the options dialog is accepted.
+%End
+
+ virtual void cancel();
+%Docstring
+Called to cancel settings changed in the options page (e.g. save them to
+:py:class:`QgsSettings` objects). This is usually called when the options dialog is canceled.
%End
protected:
diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt
index 46fafc67353..e02afc39fea 100644
--- a/src/app/CMakeLists.txt
+++ b/src/app/CMakeLists.txt
@@ -272,6 +272,9 @@ set(QGIS_APP_SRCS
gps/qgsgpsmarker.cpp
gps/qgsgpstoolbar.cpp
+ sensor/qgssensortablewidget.cpp
+ sensor/qgsprojectsensorsettingswidget.cpp
+
project/qgsprojectelevationsettingswidget.cpp
pluginmanager/qgspluginmanager.cpp
@@ -525,6 +528,7 @@ target_include_directories(qgis_app PUBLIC
${CMAKE_SOURCE_DIR}/src/app/layout
${CMAKE_SOURCE_DIR}/src/app/pluginmanager
${CMAKE_SOURCE_DIR}/src/app/gps
+ ${CMAKE_SOURCE_DIR}/src/app/sensor
${CMAKE_SOURCE_DIR}/src/app/dwg
${CMAKE_SOURCE_DIR}/src/app/maptools
${CMAKE_SOURCE_DIR}/src/app/mesh
diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp
index 65707f0080f..79e9d6e05aa 100644
--- a/src/app/qgisapp.cpp
+++ b/src/app/qgisapp.cpp
@@ -442,7 +442,9 @@ Q_GUI_EXPORT extern int qt_defaultDpiX();
#include "pointcloud/qgspointcloudlayerstylewidget.h"
#include "pointcloud/qgspointcloudlayersaveasdialog.h"
#include "pointcloud/qgspointcloudlayerexporter.h"
+
#include "project/qgsprojectelevationsettingswidget.h"
+#include "sensor/qgsprojectsensorsettingswidget.h"
#include "qgsmaptoolsdigitizingtechniquemanager.h"
#include "qgsmaptoolshaperegistry.h"
@@ -1507,6 +1509,7 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipBadLayers
registerMapLayerPropertiesFactory( new QgsElevationShadingRendererSettingsWidgetFactory( this ) );
registerProjectPropertiesWidgetFactory( new QgsProjectElevationSettingsWidgetFactory( this ) );
+ registerProjectPropertiesWidgetFactory( new QgsProjectSensorSettingsWidgetFactory( this ) );
activateDeactivateLayerRelatedActions( nullptr ); // after members were created
diff --git a/src/app/qgsprojectproperties.cpp b/src/app/qgsprojectproperties.cpp
index dc9a2e10731..4b2fbdd0348 100644
--- a/src/app/qgsprojectproperties.cpp
+++ b/src/app/qgsprojectproperties.cpp
@@ -182,6 +182,7 @@ QgsProjectProperties::QgsProjectProperties( QgsMapCanvas *mapCanvas, QWidget *pa
projectionSelector->setShowNoProjection( true );
connect( buttonBox->button( QDialogButtonBox::Apply ), &QAbstractButton::clicked, this, &QgsProjectProperties::apply );
+ connect( this, &QDialog::finished, this, [ = ]( int result ) { if ( result == QDialog::Rejected ) cancel(); } );
// disconnect default connection setup by initOptionsBase for accepting dialog, and insert logic
// to validate widgets before allowing dialog to be closed
@@ -1738,6 +1739,14 @@ void QgsProjectProperties::apply()
}
}
+void QgsProjectProperties::cancel()
+{
+ for ( QgsOptionsPageWidget *widget : std::as_const( mAdditionalProjectPropertiesWidgets ) )
+ {
+ widget->cancel();
+ }
+}
+
void QgsProjectProperties::lwWmsRowsInserted( const QModelIndex &parent, int first, int last )
{
Q_UNUSED( parent )
diff --git a/src/app/qgsprojectproperties.h b/src/app/qgsprojectproperties.h
index f92171a21e8..5c092c74b2f 100644
--- a/src/app/qgsprojectproperties.h
+++ b/src/app/qgsprojectproperties.h
@@ -80,6 +80,11 @@ class APP_EXPORT QgsProjectProperties : public QgsOptionsDialogBase, private Ui:
*/
void apply();
+ /**
+ * Slot called when cancel button is pressed or dialog is not accepted
+ */
+ void cancel();
+
/**
* Let the user add a scale to the list of project scales
* used in scale combobox instead of global ones.
diff --git a/src/app/sensor/qgsprojectsensorsettingswidget.cpp b/src/app/sensor/qgsprojectsensorsettingswidget.cpp
new file mode 100644
index 00000000000..936a57d0557
--- /dev/null
+++ b/src/app/sensor/qgsprojectsensorsettingswidget.cpp
@@ -0,0 +1,93 @@
+/***************************************************************************
+ qgsprojectsensorsettingswidget.cpp
+ ---------------------
+ begin : March 2022
+ copyright : (C) 2022 by Nyall Dawson
+ email : nyall dot dawson at gmail dot com
+ ***************************************************************************
+ * *
+ * 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 "qgsprojectsensorsettingswidget.h"
+
+#include "qgis.h"
+#include "qgsabstractsensor.h"
+#include "qgsapplication.h"
+#include "qgsproject.h"
+#include "qgssensormanager.h"
+
+QgsProjectSensorSettingsWidget::QgsProjectSensorSettingsWidget( QWidget *parent )
+ : QgsOptionsPageWidget( parent )
+{
+ setupUi( this );
+
+ QDomElement sensorElem = QgsProject::instance()->sensorManager()->writeXml( mPreviousSensors );
+ mPreviousSensors.appendChild( sensorElem );
+
+ const QList sensors = QgsProject::instance()->sensorManager()->sensors();
+ for ( QgsAbstractSensor *sensor : sensors )
+ {
+ if ( sensor->status() == Qgis::DeviceConnectionStatus::Connected )
+ {
+ mConnectedSensors << sensor->id();
+ }
+ }
+}
+
+void QgsProjectSensorSettingsWidget::cancel()
+{
+ // Capture connected state of current sensors even if we're about to revert as someone might have
+ // activated a sensor then closed the dialog using the window bar's close button
+ QList sensors = QgsProject::instance()->sensorManager()->sensors();
+ for ( QgsAbstractSensor *sensor : sensors )
+ {
+ if ( sensor->status() == Qgis::DeviceConnectionStatus::Connected )
+ {
+ mConnectedSensors << sensor->id();
+ }
+ }
+
+ QgsProject::instance()->sensorManager()->clear();
+ QgsProject::instance()->sensorManager()->readXml( mPreviousSensors.documentElement(), mPreviousSensors );
+
+ sensors = QgsProject::instance()->sensorManager()->sensors();
+ for ( QgsAbstractSensor *sensor : sensors )
+ {
+ if ( mConnectedSensors.contains( sensor->id() ) )
+ {
+ sensor->connectSensor();
+ }
+ }
+}
+
+void QgsProjectSensorSettingsWidget::apply()
+{
+ return;
+}
+
+bool QgsProjectSensorSettingsWidget::isValid()
+{
+ return true;
+}
+
+
+//
+// QgsProjectSensorSettingsWidgetFactory
+//
+
+QgsProjectSensorSettingsWidgetFactory::QgsProjectSensorSettingsWidgetFactory( QObject *parent )
+ : QgsOptionsWidgetFactory( tr( "Sensors" ), QgsApplication::getThemeIcon( QStringLiteral( "propertyicons/sensor.svg" ) ), QStringLiteral( "sensor" ) )
+{
+ setParent( parent );
+}
+
+
+QgsOptionsPageWidget *QgsProjectSensorSettingsWidgetFactory::createWidget( QWidget *parent ) const
+{
+ return new QgsProjectSensorSettingsWidget( parent );
+}
diff --git a/src/app/sensor/qgsprojectsensorsettingswidget.h b/src/app/sensor/qgsprojectsensorsettingswidget.h
new file mode 100644
index 00000000000..83d547d8c74
--- /dev/null
+++ b/src/app/sensor/qgsprojectsensorsettingswidget.h
@@ -0,0 +1,55 @@
+/***************************************************************************
+ qgsprojectsensorsettingswidget.h
+ ---------------------
+ begin : March 2023
+ copyright : (C) 2023 by Mathieu Pellerin
+ email : mathieu 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 QGSPROJECTSENSORSETTINGSWIDGET_H
+#define QGSPROJECTSENSORSETTINGSWIDGET_H
+
+#include "ui_qgsprojectsensorettingswidgetbase.h"
+#include "qgsoptionswidgetfactory.h"
+
+#include
+
+class QgsProjectSensorSettingsWidget : public QgsOptionsPageWidget, private Ui::QgsProjectSensorSettingsWidgetBase
+{
+ Q_OBJECT
+ public:
+
+ QgsProjectSensorSettingsWidget( QWidget *parent = nullptr );
+
+ public slots:
+
+ bool isValid() override;
+ void apply() override;
+ void cancel() override;
+
+ private:
+
+ QDomDocument mPreviousSensors;
+ QStringList mConnectedSensors;
+};
+
+
+class QgsProjectSensorSettingsWidgetFactory : public QgsOptionsWidgetFactory
+{
+ Q_OBJECT
+ public:
+ explicit QgsProjectSensorSettingsWidgetFactory( QObject *parent = nullptr );
+
+ QgsOptionsPageWidget *createWidget( QWidget *parent = nullptr ) const override;
+};
+
+
+
+#endif // QGSPROJECTSENSORSETTINGSWIDGET_H
diff --git a/src/app/sensor/qgssensortablewidget.cpp b/src/app/sensor/qgssensortablewidget.cpp
new file mode 100644
index 00000000000..cc81dca72b2
--- /dev/null
+++ b/src/app/sensor/qgssensortablewidget.cpp
@@ -0,0 +1,97 @@
+/***************************************************************************
+ qgssensortablewidget.h
+ ---------------------------------
+ begin : March 2023
+ copyright : (C) 2023 by Mathieu Pellerin
+ email : mathieu 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 "qgssensortablewidget.h"
+
+#include "qgisapp.h"
+#include "qgsapplication.h"
+#include "qgssensormanager.h"
+#include "qgssensormodel.h"
+#include "qgsproject.h"
+
+#include
+
+QgsSensorTableWidget::QgsSensorTableWidget( QWidget *parent )
+ : QgsPanelWidget( parent )
+{
+ setupUi( this );
+ setPanelTitle( tr( "Sensors List" ) );
+ setObjectName( QStringLiteral( "SensorsList" ) );
+
+ mActionConnection->setEnabled( false );
+
+ mSensorModel = new QgsSensorModel( QgsProject::instance()->sensorManager(), this );
+
+ mSensorTable->setModel( mSensorModel );
+ mSensorTable->horizontalHeader()->setSectionResizeMode( static_cast( QgsSensorModel::Column::Name ), QHeaderView::Stretch );
+ mSensorTable->setSelectionBehavior( QAbstractItemView::SelectRows );
+ mSensorTable->setSelectionMode( QAbstractItemView::SingleSelection );
+
+ connect( QgsProject::instance()->sensorManager(), &QgsSensorManager::sensorStatusChanged, this, [ = ]( const QString & id )
+ {
+ const QModelIndex index = mSensorTable->currentIndex();
+ if ( index.isValid() )
+ {
+ if ( id == mSensorModel->data( index, QgsSensorModel::SensorId ).toString() )
+ {
+ QgsAbstractSensor *sensor = mSensorModel->data( index, QgsSensorModel::Sensor ).value();
+ if ( sensor )
+ {
+ if ( sensor->status() == Qgis::DeviceConnectionStatus::Disconnected )
+ {
+ mActionConnection->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionStart.svg" ) ) );
+ }
+ else
+ {
+ mActionConnection->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionStop.svg" ) ) );
+ }
+ }
+ }
+ }
+ } );
+
+ connect( mSensorTable->selectionModel(), &QItemSelectionModel::currentChanged, this, [ = ]( const QModelIndex & current, const QModelIndex & )
+ {
+ mActionConnection->setEnabled( current.isValid() );
+ if ( current.isValid() && mSensorModel->data( current, QgsSensorModel::SensorStatus ).value() == Qgis::DeviceConnectionStatus::Connected )
+ {
+ mActionConnection->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionStop.svg" ) ) );
+ }
+ else
+ {
+ mActionConnection->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionStart.svg" ) ) );
+ }
+ } );
+
+ connect( mActionConnection, &QToolButton::clicked, this, [ = ]()
+ {
+ const QModelIndex index = mSensorTable->currentIndex();
+ if ( index.isValid() )
+ {
+ QgsAbstractSensor *sensor = mSensorModel->data( index, QgsSensorModel::Sensor ).value();
+ if ( sensor )
+ {
+ if ( sensor->status() == Qgis::DeviceConnectionStatus::Disconnected )
+ {
+ sensor->connectSensor();
+ }
+ else
+ {
+ sensor->disconnectSensor();
+ }
+ }
+ }
+ } );
+}
diff --git a/src/app/sensor/qgssensortablewidget.h b/src/app/sensor/qgssensortablewidget.h
new file mode 100644
index 00000000000..abff5484572
--- /dev/null
+++ b/src/app/sensor/qgssensortablewidget.h
@@ -0,0 +1,40 @@
+/***************************************************************************
+ qgssensortablewidget.h
+ ---------------------------------
+ begin : March 2023
+ copyright : (C) 2023 by Mathieu Pellerin
+ email : mathieu 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 QGSSENSORTABLEWIDGET_H
+#define QGSSENSORTABLEWIDGET_H
+
+#include "ui_qgssensortablewidgetbase.h"
+
+#include "qgis_app.h"
+#include "qgsdockwidget.h"
+#include "qgspanelwidget.h"
+#include "qgspanelwidgetstack.h"
+
+class QgsSensorModel;
+
+class APP_EXPORT QgsSensorTableWidget : public QgsPanelWidget, private Ui::QgsSensorTableWidgetBase
+{
+ Q_OBJECT
+
+ public:
+ QgsSensorTableWidget( QWidget *parent = nullptr );
+ ~QgsSensorTableWidget() override = default;
+
+ private:
+ QgsSensorModel *mSensorModel = nullptr;
+};
+
+#endif // QGSSENSORTABLEWIDGET_H
diff --git a/src/gui/qgsoptionswidgetfactory.h b/src/gui/qgsoptionswidgetfactory.h
index 1aaf4048b2c..001e3606ca9 100644
--- a/src/gui/qgsoptionswidgetfactory.h
+++ b/src/gui/qgsoptionswidgetfactory.h
@@ -77,6 +77,12 @@ class GUI_EXPORT QgsOptionsPageWidget : public QWidget
*/
virtual void apply() = 0;
+ /**
+ * Called to cancel settings changed in the options page (e.g. save them to
+ * QgsSettings objects). This is usually called when the options dialog is canceled.
+ */
+ virtual void cancel() { return; }
+
protected:
/**
diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt
index 96a41bad782..da8025925fa 100644
--- a/src/ui/CMakeLists.txt
+++ b/src/ui/CMakeLists.txt
@@ -17,6 +17,7 @@ file(GLOB NUMERICFORMAT_UIS "${CMAKE_CURRENT_SOURCE_DIR}/numericformats/*.ui")
file(GLOB ATTRIBUTEFORMCONFIG_UIS "${CMAKE_CURRENT_SOURCE_DIR}/attributeformconfig/*.ui")
file(GLOB GEOREFERENCER_UIS "${CMAKE_CURRENT_SOURCE_DIR}/georeferencer/*.ui")
file(GLOB ANNOTATION_UIS "${CMAKE_CURRENT_SOURCE_DIR}/annotations/*.ui")
+file(GLOB SENSOR_UIS "${CMAKE_CURRENT_SOURCE_DIR}/sensor/*.ui")
if (BUILD_WITH_QT6)
QT6_WRAP_UI(QGIS_UIS_H
@@ -38,6 +39,7 @@ if (BUILD_WITH_QT6)
${ATTRIBUTEFORMCONFIG_UIS}
${GEOREFERENCER_UIS}
${ANNOTATION_UIS}
+ ${SENSOR_UIS}
)
else()
QT5_WRAP_UI(QGIS_UIS_H
@@ -59,6 +61,7 @@ else()
${ATTRIBUTEFORMCONFIG_UIS}
${GEOREFERENCER_UIS}
${ANNOTATION_UIS}
+ ${SENSOR_UIS}
)
endif()
diff --git a/src/ui/sensor/qgsprojectsensorettingswidgetbase.ui b/src/ui/sensor/qgsprojectsensorettingswidgetbase.ui
new file mode 100644
index 00000000000..b709803b3ef
--- /dev/null
+++ b/src/ui/sensor/qgsprojectsensorettingswidgetbase.ui
@@ -0,0 +1,53 @@
+
+
+ QgsProjectSensorSettingsWidgetBase
+
+
+
+ 0
+ 0
+ 504
+ 486
+
+
+
+ Sensor Settings
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+ Sensor Settings
+
+
+
-
+
+
+
+
+
+
+
+
+
+ QgsSensorTableWidget
+ QWidget
+
+ 1
+
+
+
+
+
diff --git a/src/ui/sensor/qgssensortablewidgetbase.ui b/src/ui/sensor/qgssensortablewidgetbase.ui
new file mode 100644
index 00000000000..75a825c41c0
--- /dev/null
+++ b/src/ui/sensor/qgssensortablewidgetbase.ui
@@ -0,0 +1,90 @@
+
+
+ QgsSensorTableWidgetBase
+
+
+
+ 0
+ 0
+ 291
+ 376
+
+
+
+ Sensors Table
+
+
+
+ 3
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+ true
+
+
+ true
+
+
+ false
+
+
+
+ -
+
+
-
+
+
+ Toggle sensor connection status
+
+
+
+
+
+
+ :/images/themes/default/mActionStart.svg:/images/themes/default/mActionStart.svg
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
+
+ QgsPanelWidget
+ QgsPanelWidget
+
+ 1
+
+
+
+
+
+
+