From 95c57b134c6ee7551c4b8878e695a6b3f6aed057 Mon Sep 17 00:00:00 2001 From: Mathieu Pellerin Date: Tue, 21 Mar 2023 14:03:37 +0700 Subject: [PATCH] [ui][sensors] Sensors GUI registry implementation --- doc/CMakeLists.txt | 1 + images/images.qrc | 1 + images/themes/default/mSensor.svg | 2 + python/gui/auto_generated/qgsgui.sip.in | 8 + .../sensor/qgssensorguiregistry.sip.in | 156 ++++++++++ .../sensor/qgssensorwidget.sip.in | 72 +++++ python/gui/gui_auto.sip | 2 + .../sensor/qgsprojectsensorsettingswidget.cpp | 18 ++ src/app/sensor/qgssensortablewidget.cpp | 116 ++++++++ src/app/sensor/qgssensortablewidget.h | 22 +- src/gui/CMakeLists.txt | 7 + src/gui/qgsgui.cpp | 10 + src/gui/qgsgui.h | 9 + src/gui/sensor/qgssensorguiregistry.cpp | 113 ++++++++ src/gui/sensor/qgssensorguiregistry.h | 272 ++++++++++++++++++ src/gui/sensor/qgssensorwidget.cpp | 228 +++++++++++++++ src/gui/sensor/qgssensorwidget.h | 166 +++++++++++ src/ui/qgisapp.ui | 15 + src/ui/qgsprojectpropertiesbase.ui | 2 +- .../qgsprojectsensorettingswidgetbase.ui | 8 +- src/ui/sensor/qgssensorsettingswidgetbase.ui | 81 ++++++ src/ui/sensor/qgssensortablewidgetbase.ui | 47 ++- src/ui/sensor/widget_serialportsensor.ui | 69 +++++ src/ui/sensor/widget_tcpsocketsensor.ui | 98 +++++++ src/ui/sensor/widget_udpsocketsensor.ui | 98 +++++++ 25 files changed, 1611 insertions(+), 10 deletions(-) create mode 100644 images/themes/default/mSensor.svg create mode 100644 python/gui/auto_generated/sensor/qgssensorguiregistry.sip.in create mode 100644 python/gui/auto_generated/sensor/qgssensorwidget.sip.in create mode 100644 src/gui/sensor/qgssensorguiregistry.cpp create mode 100644 src/gui/sensor/qgssensorguiregistry.h create mode 100644 src/gui/sensor/qgssensorwidget.cpp create mode 100644 src/gui/sensor/qgssensorwidget.h create mode 100644 src/ui/sensor/qgssensorsettingswidgetbase.ui create mode 100644 src/ui/sensor/widget_serialportsensor.ui create mode 100644 src/ui/sensor/widget_tcpsocketsensor.ui create mode 100644 src/ui/sensor/widget_udpsocketsensor.ui diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index e2a610dc1e6..ef031704aaf 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -122,6 +122,7 @@ if(WITH_APIDOC) ${CMAKE_SOURCE_DIR}/src/gui/processing ${CMAKE_SOURCE_DIR}/src/gui/processing/models ${CMAKE_SOURCE_DIR}/src/gui/raster + ${CMAKE_SOURCE_DIR}/src/gui/sensor ${CMAKE_SOURCE_DIR}/src/gui/settings ${CMAKE_SOURCE_DIR}/src/gui/symbology ${CMAKE_SOURCE_DIR}/src/gui/tableeditor diff --git a/images/images.qrc b/images/images.qrc index 63dcfdfd7c8..eae6d5c739a 100644 --- a/images/images.qrc +++ b/images/images.qrc @@ -797,6 +797,7 @@ themes/default/mActionRegularPolygonCenterPoint.svg themes/default/3d.svg themes/default/mActionResizeSquare.svg + themes/default/mSensor.svg themes/default/mSourceFields.svg themes/default/cursors/mCapturePoint.svg themes/default/cursors/mCrossHair.svg diff --git a/images/themes/default/mSensor.svg b/images/themes/default/mSensor.svg new file mode 100644 index 00000000000..9cc5eea4a90 --- /dev/null +++ b/images/themes/default/mSensor.svg @@ -0,0 +1,2 @@ + + diff --git a/python/gui/auto_generated/qgsgui.sip.in b/python/gui/auto_generated/qgsgui.sip.in index e3f1c0fa49f..6907c2f9a46 100644 --- a/python/gui/auto_generated/qgsgui.sip.in +++ b/python/gui/auto_generated/qgsgui.sip.in @@ -10,6 +10,7 @@ + class QgsGui : QObject { %Docstring(signature="appended") @@ -130,6 +131,13 @@ Returns the global GUI-related project storage registry Returns the registry of GUI-related components of data providers .. versionadded:: 3.10 +%End + + static QgsSensorGuiRegistry *sensorGuiRegistry() /KeepReference/; +%Docstring +Returns the registry of GUI-related components for sensors + +.. versionadded:: 3.32 %End static QgsSubsetStringEditorProviderRegistry *subsetStringEditorProviderRegistry() /KeepReference/; diff --git a/python/gui/auto_generated/sensor/qgssensorguiregistry.sip.in b/python/gui/auto_generated/sensor/qgssensorguiregistry.sip.in new file mode 100644 index 00000000000..fcbaf0a2de2 --- /dev/null +++ b/python/gui/auto_generated/sensor/qgssensorguiregistry.sip.in @@ -0,0 +1,156 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/sensor/qgssensorguiregistry.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + +class QgsSensorAbstractGuiMetadata +{ +%Docstring(signature="appended") +Stores GUI metadata about one sensor class. + +This is a companion to :py:class:`QgsSensorAbstractMetadata`, storing only +the components related to the GUI behavior of sensor. + +.. note:: + + In C++ you can use :py:class:`QgsSensorGuiMetadata` convenience class. + +.. versionadded:: 3.32 +%End + +%TypeHeaderCode +#include "qgssensorguiregistry.h" +%End + public: + + QgsSensorAbstractGuiMetadata( const QString &type, const QString &visibleName ); +%Docstring +Constructor for QgsSensorAbstractGuiMetadata with the specified class ``type``. + +``visibleName`` should be set to a translated, user visible name identifying the corresponding sensor type. +%End + + virtual ~QgsSensorAbstractGuiMetadata(); + + QString type() const; +%Docstring +Returns the unique type code for the sensor class. +%End + + QString visibleName() const; +%Docstring +Returns a translated, user visible name identifying the corresponding sensor. +%End + + virtual QIcon creationIcon() const; +%Docstring +Returns an icon representing creation of the sensor type. +%End + + + virtual QgsAbstractSensorWidget *createSensorWidget( QgsAbstractSensor *sensor ) /TransferBack/; +%Docstring +Creates a configuration widget for an ``sensor`` of this type. Can return ``None`` if no configuration GUI is required. +%End + + virtual QgsAbstractSensor *createSensor( QObject *parent ) /TransferBack/; +%Docstring +Creates an instance of the corresponding sensor type. +%End + +}; + + + +class QgsSensorGuiRegistry : QObject +{ +%Docstring(signature="appended") +Registry of available sensor GUI behavior. + +:py:class:`QgsSensorGuiRegistry` is not usually directly created, but rather accessed through +:py:func:`QgsGui.sensorGuiRegistry()`. + +This acts as a companion to :py:class:`QgsSensorRegistry`, handling only +the components related to the GUI behavior of sensors. + +.. versionadded:: 3.32 +%End + +%TypeHeaderCode +#include "qgssensorguiregistry.h" +%End + public: + + QgsSensorGuiRegistry( QObject *parent = 0 ); +%Docstring +Creates a new empty sensor GUI registry. + +QgsSensorGuiRegistry is not usually directly created, but rather accessed through +:py:func:`QgsGui.sensorGuiRegistry()`. +%End + ~QgsSensorGuiRegistry(); + + + bool populate(); +%Docstring +Populates the registry with standard sensor types. If called on a non-empty registry +then this will have no effect and will return ``False``. +%End + + QgsSensorAbstractGuiMetadata *sensorMetadata( const QString &type ) const; +%Docstring +Returns the metadata for the specified sensor ``type``. Returns ``None`` if +a corresponding sensor type was not found in the registry. +%End + + bool addSensorGuiMetadata( QgsSensorAbstractGuiMetadata *metadata /Transfer/ ); +%Docstring +Registers the GUI metadata for a new sensor type. Takes ownership of the metadata instance. +%End + + + QgsAbstractSensor *createSensor( const QString &type, QObject *parent = 0 ) const /TransferBack/; +%Docstring +Creates a new instance of a sensor given the ``type``. +%End + + QgsAbstractSensorWidget *createSensorWidget( QgsAbstractSensor *sensor ) const /TransferBack/; +%Docstring +Creates a new instance of a sensor configuration widget for the specified ``sensor``. The +``sensor`` doesn't need to live for the duration of the widget, it is only used when creating +the configuration widget to match a sensor type and initiate the widget to match the +``sensor`` settings. +%End + + QMap sensorTypes() const; +%Docstring +Returns a list of sensor types handled by the registry. +%End + + signals: + + void sensorAdded( const QString &type, const QString &name ); +%Docstring +Emitted whenever a new sensor type is added to the registry, with the specified +``type``. +%End + + private: + QgsSensorGuiRegistry( const QgsSensorGuiRegistry &rh ); +}; + + + + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/sensor/qgssensorguiregistry.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/gui/auto_generated/sensor/qgssensorwidget.sip.in b/python/gui/auto_generated/sensor/qgssensorwidget.sip.in new file mode 100644 index 00000000000..6deae9b5456 --- /dev/null +++ b/python/gui/auto_generated/sensor/qgssensorwidget.sip.in @@ -0,0 +1,72 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/sensor/qgssensorwidget.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + +class QgsAbstractSensorWidget : QWidget +{ +%Docstring(signature="appended") +Base class for widgets which allow control over the properties of sensors. + +.. versionadded:: 3.32 +%End + +%TypeHeaderCode +#include "qgssensorwidget.h" +%End + public: + + QgsAbstractSensorWidget( QWidget *parent /TransferThis/ = 0 ); +%Docstring +Constructor for QgsAbstractSensorWidget. + +:param parent: parent widget +%End + + virtual QgsAbstractSensor *createSensor() = 0 /Factory/; +%Docstring +Creates a new sensor matching the settings defined in the widget. +%End + + virtual bool updateSensor( QgsAbstractSensor *sensor ) = 0; +%Docstring +Updates an existing ``sensor`` to match the settings defined in the widget. If +``True`` is returned, the ``sensor`` was successfully updated. + +If ``False`` is returned, then the widget could not successfully update +the ``sensor``. +%End + + virtual bool setSensor( QgsAbstractSensor *sensor ) = 0; +%Docstring +Sets the widget settings to match a given ``sensor``. If ``True`` is returned, ``sensor`` +was an acceptable type and the widget has been updated to match +the ``sensor``'s properties. + +If ``False`` is returned, then the widget could not be successfully updated +to show the properties of ``sensor``. +%End + + signals: + + void changed(); +%Docstring +Emitted whenever configuration changes happened on this sensor configuration. +%End +}; + + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/sensor/qgssensorwidget.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/gui/gui_auto.sip b/python/gui/gui_auto.sip index fd345ea9cff..d28fd64217e 100644 --- a/python/gui/gui_auto.sip +++ b/python/gui/gui_auto.sip @@ -476,6 +476,8 @@ %Include auto_generated/symbology/qgssymbolslistwidget.sip %Include auto_generated/symbology/qgssymbolwidgetcontext.sip %Include auto_generated/symbology/qgsvectorfieldsymbollayerwidget.sip +%Include auto_generated/sensor/qgssensorguiregistry.sip +%Include auto_generated/sensor/qgssensorwidget.sip %Include auto_generated/settings/qgssettingsregistrygui.sip %Include auto_generated/tableeditor/qgstableeditordialog.sip %Include auto_generated/tableeditor/qgstableeditorwidget.sip diff --git a/src/app/sensor/qgsprojectsensorsettingswidget.cpp b/src/app/sensor/qgsprojectsensorsettingswidget.cpp index 936a57d0557..3e6f9598115 100644 --- a/src/app/sensor/qgsprojectsensorsettingswidget.cpp +++ b/src/app/sensor/qgsprojectsensorsettingswidget.cpp @@ -20,12 +20,16 @@ #include "qgsapplication.h" #include "qgsproject.h" #include "qgssensormanager.h" +#include "qgssensortablewidget.h" QgsProjectSensorSettingsWidget::QgsProjectSensorSettingsWidget( QWidget *parent ) : QgsOptionsPageWidget( parent ) { setupUi( this ); + QgsSensorTableWidget *widget = new QgsSensorTableWidget( this ); + mPanelStack->setMainPanel( widget ); + QDomElement sensorElem = QgsProject::instance()->sensorManager()->writeXml( mPreviousSensors ); mPreviousSensors.appendChild( sensorElem ); @@ -67,6 +71,20 @@ void QgsProjectSensorSettingsWidget::cancel() void QgsProjectSensorSettingsWidget::apply() { + mPreviousSensors = QDomDocument(); + QDomElement sensorElem = QgsProject::instance()->sensorManager()->writeXml( mPreviousSensors ); + mPreviousSensors.appendChild( sensorElem ); + + mConnectedSensors.clear(); + const QList sensors = QgsProject::instance()->sensorManager()->sensors(); + for ( QgsAbstractSensor *sensor : sensors ) + { + if ( sensor->status() == Qgis::DeviceConnectionStatus::Connected ) + { + mConnectedSensors << sensor->id(); + } + } + return; } diff --git a/src/app/sensor/qgssensortablewidget.cpp b/src/app/sensor/qgssensortablewidget.cpp index cc81dca72b2..9932faf8fce 100644 --- a/src/app/sensor/qgssensortablewidget.cpp +++ b/src/app/sensor/qgssensortablewidget.cpp @@ -17,12 +17,86 @@ #include "qgisapp.h" #include "qgsapplication.h" +#include "qgsgui.h" +#include "qgsiodevicesensor.h" +#include "qgssensorguiregistry.h" #include "qgssensormanager.h" #include "qgssensormodel.h" +#include "qgssensorwidget.h" #include "qgsproject.h" +#include #include + +QgsSensorSettingsWidget::QgsSensorSettingsWidget( QgsAbstractSensor *sensor, QWidget *parent ) + : QgsPanelWidget( parent ) + , mSensor( sensor ) +{ + setupUi( this ); + setPanelTitle( tr( "Sensor Settings" ) ); + setObjectName( QStringLiteral( "SensorSettings" ) ); + + mNameLineEdit->setText( sensor->name() ); + connect( mNameLineEdit, &QLineEdit::textChanged, this, [ = ]() { mButtonBox->button( QDialogButtonBox::Apply )->setEnabled( true ); } ); + + const QMap sensorTypes = QgsGui::sensorGuiRegistry()->sensorTypes(); + for ( auto sensorIt = sensorTypes.begin(); sensorIt != sensorTypes.end(); ++sensorIt ) + { + mTypeComboBox->addItem( QgsGui::sensorGuiRegistry()->sensorMetadata( sensorIt.key() )->creationIcon(), sensorIt.value(), sensorIt.key() ); + } + mTypeComboBox->setCurrentIndex( mTypeComboBox->findData( sensor->type() ) ); + connect( mTypeComboBox, static_cast( &QComboBox::currentIndexChanged ), this, [ = ]() + { + mButtonBox->button( QDialogButtonBox::Apply )->setEnabled( true ); + setSensorWidget(); + } ); + + mButtonBox->button( QDialogButtonBox::Apply )->setEnabled( false ); + connect( mButtonBox->button( QDialogButtonBox::Apply ), &QAbstractButton::clicked, this, [ = ]() + { + apply(); + } ); + + setSensorWidget(); +} + +void QgsSensorSettingsWidget::setSensorWidget() +{ + if ( mSensorWidget ) + { + mSensorWidget->deleteLater(); + mSensorWidget = nullptr; + } + + mSensorWidget = QgsGui::sensorGuiRegistry()->sensorMetadata( mTypeComboBox->currentData().toString() )->createSensorWidget( mSensor ); + if ( mSensorWidget ) + { + mSensorWidget->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); + mSensorWidget->setSensor( mSensor ); + mTypeLayout->addWidget( mSensorWidget ); + connect( mSensorWidget, &QgsAbstractSensorWidget::changed, this, [ = ]() { mButtonBox->button( QDialogButtonBox::Apply )->setEnabled( true ); } ); + } +} + +void QgsSensorSettingsWidget::apply() +{ + if ( mSensorWidget && mSensor ) + { + mSensor->disconnectSensor(); + if ( !mSensorWidget->updateSensor( mSensor ) ) + { + // The sensor type has changed, remove sensor and add a fresh one + QgsProject::instance()->sensorManager()->removeSensor( mSensor->id() ); + mSensor = mSensorWidget->createSensor(); + QgsProject::instance()->sensorManager()->addSensor( mSensor ); + } + mSensor->setName( mNameLineEdit->text() ); + } +} + +//---------------- + QgsSensorTableWidget::QgsSensorTableWidget( QWidget *parent ) : QgsPanelWidget( parent ) { @@ -31,6 +105,8 @@ QgsSensorTableWidget::QgsSensorTableWidget( QWidget *parent ) setObjectName( QStringLiteral( "SensorsList" ) ); mActionConnection->setEnabled( false ); + mActionRemoveSensor->setEnabled( false ); + mActionEditSensor->setEnabled( false ); mSensorModel = new QgsSensorModel( QgsProject::instance()->sensorManager(), this ); @@ -39,6 +115,15 @@ QgsSensorTableWidget::QgsSensorTableWidget( QWidget *parent ) mSensorTable->setSelectionBehavior( QAbstractItemView::SelectRows ); mSensorTable->setSelectionMode( QAbstractItemView::SingleSelection ); + connect( mSensorTable, &QAbstractItemView::doubleClicked, this, [ = ]( const QModelIndex & index ) + { + if ( index.isValid() ) + { + QgsSensorSettingsWidget *settingsWidget = new QgsSensorSettingsWidget( mSensorModel->data( index, QgsSensorModel::Sensor ).value(), this ); + showPanel( settingsWidget ); + } + } ); + connect( QgsProject::instance()->sensorManager(), &QgsSensorManager::sensorStatusChanged, this, [ = ]( const QString & id ) { const QModelIndex index = mSensorTable->currentIndex(); @@ -65,6 +150,8 @@ QgsSensorTableWidget::QgsSensorTableWidget( QWidget *parent ) connect( mSensorTable->selectionModel(), &QItemSelectionModel::currentChanged, this, [ = ]( const QModelIndex & current, const QModelIndex & ) { mActionConnection->setEnabled( current.isValid() ); + mActionRemoveSensor->setEnabled( current.isValid() ); + mActionEditSensor->setEnabled( current.isValid() ); if ( current.isValid() && mSensorModel->data( current, QgsSensorModel::SensorStatus ).value() == Qgis::DeviceConnectionStatus::Connected ) { mActionConnection->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionStop.svg" ) ) ); @@ -94,4 +181,33 @@ QgsSensorTableWidget::QgsSensorTableWidget( QWidget *parent ) } } } ); + + connect( mActionAddSensor, &QToolButton::clicked, this, [ = ]() + { + QgsTcpSocketSensor *sensor = new QgsTcpSocketSensor(); + sensor->setName( tr( "New sensor" ) ); + QgsProject::instance()->sensorManager()->addSensor( sensor ); + + QgsSensorSettingsWidget *settingsWidget = new QgsSensorSettingsWidget( sensor, this ); + showPanel( settingsWidget ); + } ); + + connect( mActionRemoveSensor, &QToolButton::clicked, this, [ = ]() + { + const QModelIndex index = mSensorTable->currentIndex(); + if ( index.isValid() ) + { + QgsProject::instance()->sensorManager()->removeSensor( mSensorModel->data( index, QgsSensorModel::SensorId ).toString() ); + } + } ); + + connect( mActionEditSensor, &QToolButton::clicked, this, [ = ]() + { + const QModelIndex index = mSensorTable->currentIndex(); + if ( index.isValid() ) + { + QgsSensorSettingsWidget *settingsWidget = new QgsSensorSettingsWidget( mSensorModel->data( index, QgsSensorModel::Sensor ).value(), this ); + showPanel( settingsWidget ); + } + } ); } diff --git a/src/app/sensor/qgssensortablewidget.h b/src/app/sensor/qgssensortablewidget.h index abff5484572..8648516721f 100644 --- a/src/app/sensor/qgssensortablewidget.h +++ b/src/app/sensor/qgssensortablewidget.h @@ -17,13 +17,33 @@ #define QGSSENSORTABLEWIDGET_H #include "ui_qgssensortablewidgetbase.h" +#include "ui_qgssensorsettingswidgetbase.h" #include "qgis_app.h" #include "qgsdockwidget.h" +#include "qgsabstractsensor.h" #include "qgspanelwidget.h" -#include "qgspanelwidgetstack.h" + class QgsSensorModel; +class QgsAbstractSensorWidget; + +class APP_EXPORT QgsSensorSettingsWidget : public QgsPanelWidget, private Ui::QgsSensorSettingsWidgetBase +{ + Q_OBJECT + + public: + QgsSensorSettingsWidget( QgsAbstractSensor *sensor = nullptr, QWidget *parent SIP_TRANSFERTHIS = nullptr ); + ~QgsSensorSettingsWidget() override = default; + + void apply(); + + private: + void setSensorWidget(); + + QgsAbstractSensor *mSensor = nullptr; + QgsAbstractSensorWidget *mSensorWidget = nullptr; +}; class APP_EXPORT QgsSensorTableWidget : public QgsPanelWidget, private Ui::QgsSensorTableWidgetBase { diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 5f016c0561a..edff884f382 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -424,6 +424,9 @@ set(QGIS_GUI_SRCS providers/ogr/qgsogritemguiprovider.cpp providers/ogr/qgsgeopackageprojectstorageguiprovider.cpp + sensor/qgssensorguiregistry.cpp + sensor/qgssensorwidget.cpp + settings/qgssettingsregistrygui.cpp tableeditor/qgstableeditordialog.cpp @@ -1400,6 +1403,9 @@ set(QGIS_GUI_HDRS symbology/qgssymbolwidgetcontext.h symbology/qgsvectorfieldsymbollayerwidget.h + sensor/qgssensorguiregistry.h + sensor/qgssensorwidget.h + settings/qgssettingsregistrygui.h tableeditor/qgstableeditordialog.h @@ -1547,6 +1553,7 @@ target_include_directories(qgis_gui PUBLIC ${CMAKE_SOURCE_DIR}/src/gui/providers/ogr ${CMAKE_SOURCE_DIR}/src/gui/pointcloud ${CMAKE_SOURCE_DIR}/src/gui/raster + ${CMAKE_SOURCE_DIR}/src/gui/sensor ${CMAKE_SOURCE_DIR}/src/gui/settings ${CMAKE_SOURCE_DIR}/src/gui/tableeditor ${CMAKE_SOURCE_DIR}/src/gui/vector diff --git a/src/gui/qgsgui.cpp b/src/gui/qgsgui.cpp index 81b68f84142..86b52f1dd63 100644 --- a/src/gui/qgsgui.cpp +++ b/src/gui/qgsgui.cpp @@ -60,6 +60,7 @@ #include "qgssettingsregistrygui.h" #include "qgshistoryproviderregistry.h" #include "qgslayermetadatasourceselectprovider.h" +#include "qgssensorguiregistry.h" #include #include @@ -170,6 +171,11 @@ QgsProviderGuiRegistry *QgsGui::providerGuiRegistry() return instance()->mProviderGuiRegistry; } +QgsSensorGuiRegistry *QgsGui::sensorGuiRegistry() +{ + return instance()->mSensorGuiRegistry; +} + QgsHistoryProviderRegistry *QgsGui::historyProviderRegistry() { return instance()->mHistoryProviderRegistry; @@ -230,6 +236,7 @@ QgsGui::~QgsGui() delete mShapeMapToolRegistry; delete mRelationEditorRegistry; delete mSettingsRegistryGui; + delete mSensorGuiRegistry; } QColor QgsGui::sampleColor( QPoint point ) @@ -283,6 +290,9 @@ QgsGui::QgsGui() mCodeEditorColorSchemeRegistry = new QgsCodeEditorColorSchemeRegistry(); // provider gui registry initialize QgsProviderRegistry too + mSensorGuiRegistry = new QgsSensorGuiRegistry(); + mSensorGuiRegistry->populate(); + mHistoryProviderRegistry = new QgsHistoryProviderRegistry(); mHistoryProviderRegistry->addDefaultProviders(); diff --git a/src/gui/qgsgui.h b/src/gui/qgsgui.h index b6294b0dcf9..4329b1eeb9b 100644 --- a/src/gui/qgsgui.h +++ b/src/gui/qgsgui.h @@ -47,6 +47,8 @@ class QgsProviderSourceWidgetProviderRegistry; class QgsRelationWidgetRegistry; class QgsMapToolShapeRegistry; class QgsHistoryProviderRegistry; +class QgsSensorGuiRegistry; + /** * \ingroup gui @@ -174,6 +176,12 @@ class GUI_EXPORT QgsGui : public QObject */ static QgsProviderGuiRegistry *providerGuiRegistry() SIP_KEEPREFERENCE; + /** + * Returns the registry of GUI-related components for sensors + * \since QGIS 3.32 + */ + static QgsSensorGuiRegistry *sensorGuiRegistry() SIP_KEEPREFERENCE; + /** * Returns the registry of subset string editors of data providers * \since QGIS 3.18 @@ -313,6 +321,7 @@ class GUI_EXPORT QgsGui : public QObject QgsRelationWidgetRegistry *mRelationEditorRegistry = nullptr; QgsMapToolShapeRegistry *mShapeMapToolRegistry = nullptr; QgsHistoryProviderRegistry *mHistoryProviderRegistry = nullptr; + QgsSensorGuiRegistry *mSensorGuiRegistry = nullptr; std::unique_ptr< QgsWindowManagerInterface > mWindowManager; #ifdef SIP_RUN diff --git a/src/gui/sensor/qgssensorguiregistry.cpp b/src/gui/sensor/qgssensorguiregistry.cpp new file mode 100644 index 00000000000..37f12825148 --- /dev/null +++ b/src/gui/sensor/qgssensorguiregistry.cpp @@ -0,0 +1,113 @@ +/*************************************************************************** + qgssensorguiregistry.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 "qgsconfig.h" + +#include "qgssensorguiregistry.h" +#include "qgssensorwidget.h" + +QgsSensorGuiRegistry::QgsSensorGuiRegistry( QObject *parent ) + : QObject( parent ) +{ +} + +QgsSensorGuiRegistry::~QgsSensorGuiRegistry() +{ + qDeleteAll( mMetadata ); +} + +bool QgsSensorGuiRegistry::populate() +{ + if ( !mMetadata.isEmpty() ) + return false; + + addSensorGuiMetadata( new QgsSensorGuiMetadata( QStringLiteral( "tcp_socket" ), + QObject::tr( "TCP socket sensor" ), + QgsApplication::getThemeIcon( QStringLiteral( "/mSensor.svg" ) ), + [ = ]( QgsAbstractSensor * sensor )->QgsAbstractSensorWidget * + { + QgsTcpSocketSensorWidget *widget = new QgsTcpSocketSensorWidget( nullptr ); + widget->setSensor( sensor ); + return widget; + }, nullptr ) ); + addSensorGuiMetadata( new QgsSensorGuiMetadata( QStringLiteral( "udp_socket" ), + QObject::tr( "UDP socket sensor" ), + QgsApplication::getThemeIcon( QStringLiteral( "/mSensor.svg" ) ), + [ = ]( QgsAbstractSensor * sensor )->QgsAbstractSensorWidget * + { + QgsUdpSocketSensorWidget *widget = new QgsUdpSocketSensorWidget( nullptr ); + widget->setSensor( sensor ); + return widget; + }, nullptr ) ); +#if defined( HAVE_QTSERIALPORT ) + addSensorGuiMetadata( new QgsSensorGuiMetadata( QStringLiteral( "serial_port" ), + QObject::tr( "Serial port sensor" ), + QgsApplication::getThemeIcon( QStringLiteral( "/mSensor.svg" ) ), + [ = ]( QgsAbstractSensor * sensor )->QgsAbstractSensorWidget * + { + QgsSerialPortSensorWidget *widget = new QgsSerialPortSensorWidget( nullptr ); + widget->setSensor( sensor ); + return widget; + }, nullptr ) ); +#endif + return true; +} + +QgsSensorAbstractGuiMetadata *QgsSensorGuiRegistry::sensorMetadata( const QString &type ) const +{ + return mMetadata.value( type ); +} + +bool QgsSensorGuiRegistry::addSensorGuiMetadata( QgsSensorAbstractGuiMetadata *metadata ) +{ + if ( !metadata || mMetadata.contains( metadata->type() ) ) + return false; + + mMetadata[metadata->type()] = metadata; + emit sensorAdded( metadata->type(), metadata->visibleName() ); + return true; +} + +QgsAbstractSensor *QgsSensorGuiRegistry::createSensor( const QString &type, QObject *parent ) const +{ + if ( !mMetadata.contains( type ) ) + return nullptr; + + std::unique_ptr< QgsAbstractSensor > sensor( mMetadata.value( type )->createSensor( parent ) ); + if ( sensor ) + return sensor.release(); + + return QgsApplication::sensorRegistry()->createSensor( type, parent ); +} + +QgsAbstractSensorWidget *QgsSensorGuiRegistry::createSensorWidget( QgsAbstractSensor *sensor ) const +{ + if ( !sensor || !mMetadata.contains( sensor->type() ) ) + return nullptr; + + return mMetadata[sensor->type()]->createSensorWidget( sensor ); +} + +QMap QgsSensorGuiRegistry::sensorTypes() const +{ + QMap types; + for ( auto it = mMetadata.constBegin(); it != mMetadata.constEnd(); ++it ) + { + types.insert( it.key(), it.value()->visibleName() ); + } + + return types; +} diff --git a/src/gui/sensor/qgssensorguiregistry.h b/src/gui/sensor/qgssensorguiregistry.h new file mode 100644 index 00000000000..fd7b1e3b8ed --- /dev/null +++ b/src/gui/sensor/qgssensorguiregistry.h @@ -0,0 +1,272 @@ +/*************************************************************************** + qgssensorguiregistry.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 QGSSENSORGUIREGISTRY_H +#define QGSSENSORGUIREGISTRY_H + +#include "qgis_gui.h" +#include "qgis_sip.h" +#include "qgsabstractsensor.h" +#include "qgssensorregistry.h" +#include "qgssensorwidget.h" + +#include + +/** + * \ingroup gui + * \brief Stores GUI metadata about one sensor class. + * + * This is a companion to QgsSensorAbstractMetadata, storing only + * the components related to the GUI behavior of sensor. + * + * \note In C++ you can use QgsSensorGuiMetadata convenience class. + * \since QGIS 3.32 + */ +class GUI_EXPORT QgsSensorAbstractGuiMetadata +{ + public: + + /** + * Constructor for QgsSensorAbstractGuiMetadata with the specified class \a type. + * + * \a visibleName should be set to a translated, user visible name identifying the corresponding sensor type. + */ + QgsSensorAbstractGuiMetadata( const QString &type, const QString &visibleName ) + : mType( type ) + , mVisibleName( visibleName ) + {} + + virtual ~QgsSensorAbstractGuiMetadata() = default; + + /** + * Returns the unique type code for the sensor class. + */ + QString type() const { return mType; } + + /** + * Returns a translated, user visible name identifying the corresponding sensor. + */ + QString visibleName() const { return mVisibleName; } + + /** + * Returns an icon representing creation of the sensor type. + */ + virtual QIcon creationIcon() const { return QgsApplication::getThemeIcon( QStringLiteral( "/mSensor.svg" ) ); } + + /* + * IMPORTANT: While it seems like /Factory/ would be the correct annotations here, that's not + * the case. + * As per Phil Thomson's advice on https://www.riverbankcomputing.com/pipermail/pyqt/2017-July/039450.html: + * + * " + * /Factory/ is used when the instance returned is guaranteed to be new to Python. + * In this case it isn't because it has already been seen when being returned by QgsProcessingAlgorithm::createInstance() + * (However for a different sub-class implemented in C++ then it would be the first time it was seen + * by Python so the /Factory/ on create() would be correct.) + * + * You might try using /TransferBack/ on create() instead - that might be the best compromise. + * " + */ + + /** + * Creates a configuration widget for an \a sensor of this type. Can return NULLPTR if no configuration GUI is required. + */ + virtual QgsAbstractSensorWidget *createSensorWidget( QgsAbstractSensor *sensor ) SIP_TRANSFERBACK { Q_UNUSED( sensor ) return nullptr; } + + /** + * Creates an instance of the corresponding sensor type. + */ + virtual QgsAbstractSensor *createSensor( QObject *parent ) SIP_TRANSFERBACK { Q_UNUSED( parent ) return nullptr; } + + private: + + QString mType; + QString mVisibleName; + +}; + +//! Sensor configuration widget creation function +typedef std::function QgsSensorWidgetFunc SIP_SKIP; + +#ifndef SIP_RUN + +/** + * \ingroup gui + * \brief Convenience metadata class that uses static functions to handle sensor GUI behavior. + * \note not available in Python bindings + * \since QGIS 3.0 + */ +class GUI_EXPORT QgsSensorGuiMetadata : public QgsSensorAbstractGuiMetadata +{ + public: + + /** + * Constructor for QgsSensorGuiMetadata with the specified class \a type + * and \a creationIcon, and function pointers for the configuration widget creation function. + * + * \a visibleName should be set to a translated, user visible name identifying the corresponding sensor. + */ + QgsSensorGuiMetadata( const QString &type, const QString &visibleName, const QIcon &creationIcon, + const QgsSensorWidgetFunc &pfWidget = nullptr, + const QgsSensorCreateFunc &pfCreateFunc = nullptr ) + : QgsSensorAbstractGuiMetadata( type, visibleName ) + , mIcon( creationIcon ) + , mWidgetFunc( pfWidget ) + , mCreateFunc( pfCreateFunc ) + {} + + /** + * Returns the classes' configuration widget creation function. + * \see setWidgetFunction() + */ + QgsSensorWidgetFunc widgetFunction() const { return mWidgetFunc; } + + /** + * Sets the classes' sensor configuration widget creation \a function. + * \see widgetFunction() + */ + void setWidgetFunction( const QgsSensorWidgetFunc &function ) { mWidgetFunc = function; } + + /** + * Returns the classes' sensor creation function. + * \see setSensorCreationFunction() + */ + QgsSensorCreateFunc sensorCreationFunction() const { return mCreateFunc; } + + /** + * Sets the classes' sensor creation \a function. + * \see sensorCreationFunction() + */ + void setSensorCreationFunction( const QgsSensorCreateFunc &function ) { mCreateFunc = function; } + + QIcon creationIcon() const override { return mIcon.isNull() ? QgsSensorAbstractGuiMetadata::creationIcon() : mIcon; } + QgsAbstractSensorWidget *createSensorWidget( QgsAbstractSensor *sensor ) override { return mWidgetFunc ? mWidgetFunc( sensor ) : nullptr; } + QgsAbstractSensor *createSensor( QObject *parent ) override { return mCreateFunc ? mCreateFunc( parent ) : nullptr; } + + protected: + + QIcon mIcon; + QgsSensorWidgetFunc mWidgetFunc = nullptr; + QgsSensorCreateFunc mCreateFunc = nullptr; + +}; + +#endif + +/** + * \ingroup gui + * \class QgsSensorGuiRegistry + * \brief Registry of available sensor GUI behavior. + * + * QgsSensorGuiRegistry is not usually directly created, but rather accessed through + * QgsGui::sensorGuiRegistry(). + * + * This acts as a companion to QgsSensorRegistry, handling only + * the components related to the GUI behavior of sensors. + * + * \since QGIS 3.32 + */ +class GUI_EXPORT QgsSensorGuiRegistry : public QObject +{ + Q_OBJECT + + public: + + /** + * Creates a new empty sensor GUI registry. + * + * QgsSensorGuiRegistry is not usually directly created, but rather accessed through + * QgsGui::sensorGuiRegistry(). + */ + QgsSensorGuiRegistry( QObject *parent = nullptr ); + ~QgsSensorGuiRegistry() override; + + //! QgsSensorGuiRegistry cannot be copied. + QgsSensorGuiRegistry( const QgsSensorGuiRegistry &rh ) = delete; + //! QgsSensorGuiRegistry cannot be copied. + QgsSensorGuiRegistry &operator=( const QgsSensorGuiRegistry &rh ) = delete; + + /** + * Populates the registry with standard sensor types. If called on a non-empty registry + * then this will have no effect and will return FALSE. + */ + bool populate(); + + /** + * Returns the metadata for the specified sensor \a type. Returns NULLPTR if + * a corresponding sensor type was not found in the registry. + */ + QgsSensorAbstractGuiMetadata *sensorMetadata( const QString &type ) const; + + /** + * Registers the GUI metadata for a new sensor type. Takes ownership of the metadata instance. + */ + bool addSensorGuiMetadata( QgsSensorAbstractGuiMetadata *metadata SIP_TRANSFER ); + + /* + * IMPORTANT: While it seems like /Factory/ would be the correct annotations here, that's not + * the case. + * As per Phil Thomson's advice on https://www.riverbankcomputing.com/pipermail/pyqt/2017-July/039450.html: + * + * " + * /Factory/ is used when the instance returned is guaranteed to be new to Python. + * In this case it isn't because it has already been seen when being returned by QgsProcessingAlgorithm::createInstance() + * (However for a different sub-class implemented in C++ then it would be the first time it was seen + * by Python so the /Factory/ on create() would be correct.) + * + * You might try using /TransferBack/ on create() instead - that might be the best compromise. + * " + */ + + /** + * Creates a new instance of a sensor given the \a type. + */ + QgsAbstractSensor *createSensor( const QString &type, QObject *parent = nullptr ) const SIP_TRANSFERBACK; + + /** + * Creates a new instance of a sensor configuration widget for the specified \a sensor. The + * \a sensor doesn't need to live for the duration of the widget, it is only used when creating + * the configuration widget to match a sensor type and initiate the widget to match the + * \a sensor settings. + */ + QgsAbstractSensorWidget *createSensorWidget( QgsAbstractSensor *sensor ) const SIP_TRANSFERBACK; + + /** + * Returns a list of sensor types handled by the registry. + */ + QMap sensorTypes() const; + + signals: + + /** + * Emitted whenever a new sensor type is added to the registry, with the specified + * \a type. + */ + void sensorAdded( const QString &type, const QString &name ); + + private: + +#ifdef SIP_RUN + QgsSensorGuiRegistry( const QgsSensorGuiRegistry &rh ); +#endif + + QMap mMetadata; + +}; + +#endif //QGSSENSORGUIREGISTRY_H + + + diff --git a/src/gui/sensor/qgssensorwidget.cpp b/src/gui/sensor/qgssensorwidget.cpp new file mode 100644 index 00000000000..b15a5903291 --- /dev/null +++ b/src/gui/sensor/qgssensorwidget.cpp @@ -0,0 +1,228 @@ +/*************************************************************************** + qgssensorwidget.cpp + --------------------- + 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 "qgsconfig.h" + +#include "qgssensorwidget.h" +#include "qgsiodevicesensor.h" + +#if defined( HAVE_QTSERIALPORT ) +#include +#include +#endif + +QgsAbstractSensorWidget::QgsAbstractSensorWidget( QWidget *parent SIP_TRANSFERTHIS ) + : QWidget( parent ) +{ +} + +// ------------------------ + +///@cond PRIVATE + +QgsTcpSocketSensorWidget::QgsTcpSocketSensorWidget( QWidget *parent ) + : QgsAbstractSensorWidget( parent ) +{ + setupUi( this ); + + connect( mHostNameLineEdit, &QLineEdit::textChanged, this, &QgsAbstractSensorWidget::changed ); + connect( mPortSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsAbstractSensorWidget::changed ); +} + +QgsAbstractSensor *QgsTcpSocketSensorWidget::createSensor() +{ + QgsTcpSocketSensor *s = new QgsTcpSocketSensor(); + s->setHostName( mHostNameLineEdit->text() ); + s->setPort( mPortSpinBox->value() ); + return s; +} + +bool QgsTcpSocketSensorWidget::updateSensor( QgsAbstractSensor *sensor ) +{ + QgsTcpSocketSensor *s = dynamic_cast( sensor ); + if ( !s ) + return false; + + s->setHostName( mHostNameLineEdit->text() ); + s->setPort( mPortSpinBox->value() ); + + return true; +} + +bool QgsTcpSocketSensorWidget::setSensor( QgsAbstractSensor *sensor ) +{ + QgsTcpSocketSensor *ts = dynamic_cast( sensor ); + if ( ts ) + { + mHostNameLineEdit->setText( ts->hostName() ); + mPortSpinBox->setValue( ts->port() ); + return true; + } + + QgsUdpSocketSensor *us = dynamic_cast( sensor ); + if ( us ) + { + mHostNameLineEdit->setText( us->hostName() ); + mPortSpinBox->setValue( us->port() ); + return true; + } + + return false; +} + +// ------------------------ + +QgsUdpSocketSensorWidget::QgsUdpSocketSensorWidget( QWidget *parent ) + : QgsAbstractSensorWidget( parent ) +{ + setupUi( this ); + + connect( mHostNameLineEdit, &QLineEdit::textChanged, this, &QgsAbstractSensorWidget::changed ); + connect( mPortSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsAbstractSensorWidget::changed ); +} + +QgsAbstractSensor *QgsUdpSocketSensorWidget::createSensor() +{ + QgsUdpSocketSensor *s = new QgsUdpSocketSensor(); + s->setHostName( mHostNameLineEdit->text() ); + s->setPort( mPortSpinBox->value() ); + return s; +} + +bool QgsUdpSocketSensorWidget::updateSensor( QgsAbstractSensor *sensor ) +{ + QgsUdpSocketSensor *s = dynamic_cast( sensor ); + if ( !s ) + return false; + + s->setHostName( mHostNameLineEdit->text() ); + s->setPort( mPortSpinBox->value() ); + + return true; +} + +bool QgsUdpSocketSensorWidget::setSensor( QgsAbstractSensor *sensor ) +{ + QgsTcpSocketSensor *ts = dynamic_cast( sensor ); + if ( ts ) + { + mHostNameLineEdit->setText( ts->hostName() ); + mPortSpinBox->setValue( ts->port() ); + return true; + } + + QgsUdpSocketSensor *us = dynamic_cast( sensor ); + if ( us ) + { + mHostNameLineEdit->setText( us->hostName() ); + mPortSpinBox->setValue( us->port() ); + return true; + } + + return false; +} + +// ------------------------ + +#if defined( HAVE_QTSERIALPORT ) +QgsSerialPortSensorWidget::QgsSerialPortSensorWidget( QWidget *parent ) + : QgsAbstractSensorWidget( parent ) +{ + setupUi( this ); + + for ( const QSerialPortInfo &info : QSerialPortInfo::availablePorts() ) + { + mSerialPortComboBox->addItem( QStringLiteral( "%1: %2" ).arg( info.portName(), info.description() ), info.portName() ); + } + + updateSerialPortDetails(); + + connect( mSerialPortComboBox, static_cast( &QComboBox::currentIndexChanged ), this, [ = ]() + { + updateSerialPortDetails(); + emit changed(); + } ); +} + +QgsAbstractSensor *QgsSerialPortSensorWidget::createSensor() +{ + QgsSerialPortSensor *s = new QgsSerialPortSensor(); + s->setPortName( mSerialPortComboBox->currentData().toString() ); + return s; +} + +bool QgsSerialPortSensorWidget::updateSensor( QgsAbstractSensor *sensor ) +{ + QgsSerialPortSensor *s = dynamic_cast( sensor ); + if ( !s ) + return false; + + s->setPortName( mSerialPortComboBox->currentData().toString() ); + + return true; +} + +bool QgsSerialPortSensorWidget::setSensor( QgsAbstractSensor *sensor ) +{ + QgsSerialPortSensor *s = dynamic_cast( sensor ); + if ( !s ) + return false; + + const int index = mSerialPortComboBox->findData( s->portName() ); + if ( index >= 0 ) + { + mSerialPortComboBox->setCurrentIndex( index ); + } + else + { + mSerialPortComboBox->addItem( s->portName(), s->portName() ); + mSerialPortComboBox->setCurrentIndex( mSerialPortComboBox->count() - 1 ); + } + + return true; +} + +void QgsSerialPortSensorWidget::updateSerialPortDetails() +{ + if ( mSerialPortComboBox->currentIndex() < 0 ) + { + return; + } + + const QString ¤tPortName = mSerialPortComboBox->currentData().toString(); + bool serialPortFound = false; + for ( const QSerialPortInfo &info : QSerialPortInfo::availablePorts() ) + { + serialPortFound = info.portName() == currentPortName; + if ( serialPortFound ) + { + mSerialPortDetails->setText( QStringLiteral( "%1:\n- %2: %3\n- %4: %5\n- %6: %7\n- %8: %9\n- %10: %11" ).arg( tr( "Serial port details" ), + tr( "Port name" ), info.portName(), + tr( "Description" ), info.description(), + tr( "Manufacturer" ), info.manufacturer(), + tr( "Product identifier" ), QString::number( info.productIdentifier() ), + tr( "Serial number" ), info.serialNumber() ) ); + break; + } + } + if ( !serialPortFound ) + { + mSerialPortDetails->setText( QStringLiteral( "%1:\n- %2: %3" ).arg( tr( "Serial port details" ), + tr( "Port name" ), currentPortName ) ); + } +} +#endif + +///@endcond diff --git a/src/gui/sensor/qgssensorwidget.h b/src/gui/sensor/qgssensorwidget.h new file mode 100644 index 00000000000..2510d560882 --- /dev/null +++ b/src/gui/sensor/qgssensorwidget.h @@ -0,0 +1,166 @@ +/*************************************************************************** + qgssensorwidget.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 QGSSENSORWIDGET_H +#define QGSSENSORWIDGET_H + +#include "ui_widget_tcpsocketsensor.h" +#include "ui_widget_udpsocketsensor.h" +#include "ui_widget_serialportsensor.h" + +#include "qgsconfig.h" + +#include "qgis_sip.h" +#include "qgis_gui.h" +#include "qgsabstractsensor.h" + +#include + +/** + * \ingroup gui + * \class QgsAbstractSensorWidget + * \brief Base class for widgets which allow control over the properties of sensors. + * \since QGIS 3.32 + */ +class GUI_EXPORT QgsAbstractSensorWidget : public QWidget +{ + + Q_OBJECT + + public: + + /** + * Constructor for QgsAbstractSensorWidget. + * \param parent parent widget + */ + QgsAbstractSensorWidget( QWidget *parent SIP_TRANSFERTHIS = nullptr ); + + /** + * Creates a new sensor matching the settings defined in the widget. + */ + virtual QgsAbstractSensor *createSensor() = 0 SIP_FACTORY; + + /** + * Updates an existing \a sensor to match the settings defined in the widget. If + * TRUE is returned, the \a sensor was successfully updated. + * + * If FALSE is returned, then the widget could not successfully update + * the \a sensor. + */ + virtual bool updateSensor( QgsAbstractSensor *sensor ) = 0; + + /** + * Sets the widget settings to match a given \a sensor. If TRUE is returned, \a sensor + * was an acceptable type and the widget has been updated to match + * the \a sensor's properties. + * + * If FALSE is returned, then the widget could not be successfully updated + * to show the properties of \a sensor. + */ + virtual bool setSensor( QgsAbstractSensor *sensor ) = 0; + + signals: + + /** + * Emitted whenever configuration changes happened on this sensor configuration. + */ + void changed(); +}; + +#ifndef SIP_RUN +///@cond PRIVATE + +/** + * \ingroup gui + * \class QgsTcpSocketSensorWidget + * \brief A configuration widget which allow control over QgsTcpSocketSensor properties. + * \since QGIS 3.32 + */ +class GUI_EXPORT QgsTcpSocketSensorWidget : public QgsAbstractSensorWidget, private Ui::WidgetTcpSocketSensor +{ + + Q_OBJECT + + public: + + /** + * Constructor for QgsTcpSocketSensorWidget. + * \param parent parent widget + */ + QgsTcpSocketSensorWidget( QWidget *parent ); + + QgsAbstractSensor *createSensor() override; + bool updateSensor( QgsAbstractSensor *sensor ) override; + bool setSensor( QgsAbstractSensor *sensor ) override; +}; + +/** + * \ingroup gui + * \class QgsUdpSocketSensorWidget + * \brief A configuration widget which allow control over QgsUdpSocketSensorWidget properties. + * \since QGIS 3.32 + */ +class GUI_EXPORT QgsUdpSocketSensorWidget : public QgsAbstractSensorWidget, private Ui::WidgetUdpSocketSensor +{ + + Q_OBJECT + + public: + + /** + * Constructor for QgsUdpSocketSensorWidget. + * \param parent parent widget + */ + QgsUdpSocketSensorWidget( QWidget *parent ); + + QgsAbstractSensor *createSensor() override; + bool updateSensor( QgsAbstractSensor *sensor ) override; + bool setSensor( QgsAbstractSensor *sensor ) override; +}; + +#if defined( HAVE_QTSERIALPORT ) + +/** + * \ingroup gui + * \class QgsSerialPortSensorWidget + * \brief A configuration widget which allow control over QgsSerialPortSensor properties. + * \since QGIS 3.32 + */ +class GUI_EXPORT QgsSerialPortSensorWidget : public QgsAbstractSensorWidget, private Ui::WidgetSerialPortSensor +{ + + Q_OBJECT + + public: + + /** + * Constructor for QgsSerialPortSensorWidget. + * \param parent parent widget + */ + QgsSerialPortSensorWidget( QWidget *parent ); + + QgsAbstractSensor *createSensor() override; + bool updateSensor( QgsAbstractSensor *sensor ) override; + bool setSensor( QgsAbstractSensor *sensor ) override; + + private: + + void updateSerialPortDetails(); +}; +#endif + +#endif +///@endcond + +#endif // QGSSENSORWIDGET_H diff --git a/src/ui/qgisapp.ui b/src/ui/qgisapp.ui index 28450c3e503..49ec41ea45a 100644 --- a/src/ui/qgisapp.ui +++ b/src/ui/qgisapp.ui @@ -2854,6 +2854,21 @@ Shift+click to snap rotation to 45 degree steps. Show Statistical Summary + + + true + + + + :/images/themes/default/mSensorDock.svg:/images/themes/default/mSensorDock.svg + + + Sensors + + + Show Sensors + + Align Rasters… diff --git a/src/ui/qgsprojectpropertiesbase.ui b/src/ui/qgsprojectpropertiesbase.ui index 873af9b7b86..61b788b85ce 100644 --- a/src/ui/qgsprojectpropertiesbase.ui +++ b/src/ui/qgsprojectpropertiesbase.ui @@ -3373,7 +3373,7 @@ - + diff --git a/src/ui/sensor/qgsprojectsensorettingswidgetbase.ui b/src/ui/sensor/qgsprojectsensorettingswidgetbase.ui index b709803b3ef..51792104857 100644 --- a/src/ui/sensor/qgsprojectsensorettingswidgetbase.ui +++ b/src/ui/sensor/qgsprojectsensorettingswidgetbase.ui @@ -29,11 +29,11 @@ - Sensor Settings + Sensors - + @@ -42,9 +42,9 @@ - QgsSensorTableWidget + QgsPanelWidgetStack QWidget -
qgssensortablewidget.h
+
qgspanelwidgetstack.h
1
diff --git a/src/ui/sensor/qgssensorsettingswidgetbase.ui b/src/ui/sensor/qgssensorsettingswidgetbase.ui new file mode 100644 index 00000000000..f72dcc04e10 --- /dev/null +++ b/src/ui/sensor/qgssensorsettingswidgetbase.ui @@ -0,0 +1,81 @@ + + + QgsSensorSettingsWidgetBase + + + + 0 + 0 + 291 + 376 + + + + + 3 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Sensor name + + + true + + + + + + + + + + Sensor type + + + true + + + + + + + + + + + + + + + QDialogButtonBox::Apply + + + + + + + + QgsPanelWidget + QWidget +
qgspanelwidget.h
+ 1 +
+
+ + + + +
diff --git a/src/ui/sensor/qgssensortablewidgetbase.ui b/src/ui/sensor/qgssensortablewidgetbase.ui index 75a825c41c0..9047efc521a 100644 --- a/src/ui/sensor/qgssensortablewidgetbase.ui +++ b/src/ui/sensor/qgssensortablewidgetbase.ui @@ -10,9 +10,6 @@ 376
- - Sensors Table - 3 @@ -58,6 +55,48 @@ + + + + Create new sensor + + + + + + + :/images/themes/default/symbologyAdd.svg:/images/themes/default/symbologyAdd.svg + + + + + + + Create new sensor + + + + + + + :/images/themes/default/symbologyRemove.svg:/images/themes/default/symbologyRemove.svg + + + + + + + Edit sensor + + + + + + + :/images/themes/default/symbologyEdit.svg:/images/themes/default/symbologyEdit.svg + + + @@ -78,7 +117,7 @@ QgsPanelWidget - QgsPanelWidget + QWidget
qgspanelwidget.h
1
diff --git a/src/ui/sensor/widget_serialportsensor.ui b/src/ui/sensor/widget_serialportsensor.ui new file mode 100644 index 00000000000..d79bd7ce4a6 --- /dev/null +++ b/src/ui/sensor/widget_serialportsensor.ui @@ -0,0 +1,69 @@ + + + WidgetSerialPortSensor + + + + 0 + 0 + 371 + 409 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Serial port name + + + true + + + + + + + + + + false + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/src/ui/sensor/widget_tcpsocketsensor.ui b/src/ui/sensor/widget_tcpsocketsensor.ui new file mode 100644 index 00000000000..8541182f881 --- /dev/null +++ b/src/ui/sensor/widget_tcpsocketsensor.ui @@ -0,0 +1,98 @@ + + + WidgetTcpSocketSensor + + + + 0 + 0 + 371 + 409 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Host name (a domain string or IP address) + + + true + + + + + + + + + + Port + + + true + + + + + + + + 0 + 0 + + + + + + + 1 + + + 65535 + + + false + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + QgsSpinBox + QSpinBox +
qgsspinbox.h
+
+
+ + +
diff --git a/src/ui/sensor/widget_udpsocketsensor.ui b/src/ui/sensor/widget_udpsocketsensor.ui new file mode 100644 index 00000000000..0d4f34319a2 --- /dev/null +++ b/src/ui/sensor/widget_udpsocketsensor.ui @@ -0,0 +1,98 @@ + + + WidgetUdpSocketSensor + + + + 0 + 0 + 371 + 409 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Host name (a domain string or IP address) + + + true + + + + + + + + + + Port + + + true + + + + + + + + 0 + 0 + + + + + + + 1 + + + 65535 + + + false + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + QgsSpinBox + QSpinBox +
qgsspinbox.h
+
+
+ + +