Skip to content

Commit be99a12

Browse files
authoredMar 30, 2023
Merge pull request #52376 from nirvn/sensors_framework_3
Sensors GUI registry and configuration widgets
2 parents 4419761 + f2b7751 commit be99a12

29 files changed

+1629
-11
lines changed
 

‎doc/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ if(WITH_APIDOC)
122122
${CMAKE_SOURCE_DIR}/src/gui/processing
123123
${CMAKE_SOURCE_DIR}/src/gui/processing/models
124124
${CMAKE_SOURCE_DIR}/src/gui/raster
125+
${CMAKE_SOURCE_DIR}/src/gui/sensor
125126
${CMAKE_SOURCE_DIR}/src/gui/settings
126127
${CMAKE_SOURCE_DIR}/src/gui/symbology
127128
${CMAKE_SOURCE_DIR}/src/gui/tableeditor

‎images/images.qrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -797,6 +797,7 @@
797797
<file>themes/default/mActionRegularPolygonCenterPoint.svg</file>
798798
<file>themes/default/3d.svg</file>
799799
<file>themes/default/mActionResizeSquare.svg</file>
800+
<file>themes/default/mSensor.svg</file>
800801
<file>themes/default/mSourceFields.svg</file>
801802
<file>themes/default/cursors/mCapturePoint.svg</file>
802803
<file>themes/default/cursors/mCrossHair.svg</file>

‎images/themes/default/mSensor.svg

Lines changed: 2 additions & 0 deletions
Loading

‎python/core/auto_generated/sensor/qgsiodevicesensor.sip.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99

1010

11+
12+
1113
class QgsIODeviceSensor : QgsAbstractSensor
1214
{
1315
%Docstring(signature="appended")
@@ -182,6 +184,7 @@ Sets the ``port`` the socket connects to.
182184

183185
};
184186

187+
185188
class QgsSerialPortSensor : QgsIODeviceSensor
186189
{
187190
%Docstring(signature="appended")

‎python/gui/auto_generated/qgsgui.sip.in

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111

1212

13+
1314
class QgsGui : QObject
1415
{
1516
%Docstring(signature="appended")
@@ -130,6 +131,13 @@ Returns the global GUI-related project storage registry
130131
Returns the registry of GUI-related components of data providers
131132

132133
.. versionadded:: 3.10
134+
%End
135+
136+
static QgsSensorGuiRegistry *sensorGuiRegistry() /KeepReference/;
137+
%Docstring
138+
Returns the registry of GUI-related components for sensors
139+
140+
.. versionadded:: 3.32
133141
%End
134142

135143
static QgsSubsetStringEditorProviderRegistry *subsetStringEditorProviderRegistry() /KeepReference/;
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/************************************************************************
2+
* This file has been generated automatically from *
3+
* *
4+
* src/gui/sensor/qgssensorguiregistry.h *
5+
* *
6+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
7+
************************************************************************/
8+
9+
10+
11+
class QgsSensorAbstractGuiMetadata
12+
{
13+
%Docstring(signature="appended")
14+
Stores GUI metadata about one sensor class.
15+
16+
This is a companion to :py:class:`QgsSensorAbstractMetadata`, storing only
17+
the components related to the GUI behavior of sensor.
18+
19+
.. note::
20+
21+
In C++ you can use :py:class:`QgsSensorGuiMetadata` convenience class.
22+
23+
.. versionadded:: 3.32
24+
%End
25+
26+
%TypeHeaderCode
27+
#include "qgssensorguiregistry.h"
28+
%End
29+
public:
30+
31+
QgsSensorAbstractGuiMetadata( const QString &type, const QString &visibleName );
32+
%Docstring
33+
Constructor for QgsSensorAbstractGuiMetadata with the specified class ``type``.
34+
35+
``visibleName`` should be set to a translated, user visible name identifying the corresponding sensor type.
36+
%End
37+
38+
virtual ~QgsSensorAbstractGuiMetadata();
39+
40+
QString type() const;
41+
%Docstring
42+
Returns the unique type code for the sensor class.
43+
%End
44+
45+
QString visibleName() const;
46+
%Docstring
47+
Returns a translated, user visible name identifying the corresponding sensor.
48+
%End
49+
50+
virtual QIcon creationIcon() const;
51+
%Docstring
52+
Returns an icon representing creation of the sensor type.
53+
%End
54+
55+
56+
virtual QgsAbstractSensorWidget *createSensorWidget( QgsAbstractSensor *sensor ) /TransferBack/;
57+
%Docstring
58+
Creates a configuration widget for an ``sensor`` of this type. Can return ``None`` if no configuration GUI is required.
59+
%End
60+
61+
virtual QgsAbstractSensor *createSensor( QObject *parent ) /TransferBack/;
62+
%Docstring
63+
Creates an instance of the corresponding sensor type.
64+
%End
65+
66+
};
67+
68+
69+
70+
class QgsSensorGuiRegistry : QObject
71+
{
72+
%Docstring(signature="appended")
73+
Registry of available sensor GUI behavior.
74+
75+
:py:class:`QgsSensorGuiRegistry` is not usually directly created, but rather accessed through
76+
:py:func:`QgsGui.sensorGuiRegistry()`.
77+
78+
This acts as a companion to :py:class:`QgsSensorRegistry`, handling only
79+
the components related to the GUI behavior of sensors.
80+
81+
.. versionadded:: 3.32
82+
%End
83+
84+
%TypeHeaderCode
85+
#include "qgssensorguiregistry.h"
86+
%End
87+
public:
88+
89+
QgsSensorGuiRegistry( QObject *parent = 0 );
90+
%Docstring
91+
Creates a new empty sensor GUI registry.
92+
93+
QgsSensorGuiRegistry is not usually directly created, but rather accessed through
94+
:py:func:`QgsGui.sensorGuiRegistry()`.
95+
%End
96+
~QgsSensorGuiRegistry();
97+
98+
99+
bool populate();
100+
%Docstring
101+
Populates the registry with standard sensor types. If called on a non-empty registry
102+
then this will have no effect and will return ``False``.
103+
%End
104+
105+
QgsSensorAbstractGuiMetadata *sensorMetadata( const QString &type ) const;
106+
%Docstring
107+
Returns the metadata for the specified sensor ``type``. Returns ``None`` if
108+
a corresponding sensor type was not found in the registry.
109+
%End
110+
111+
bool addSensorGuiMetadata( QgsSensorAbstractGuiMetadata *metadata /Transfer/ );
112+
%Docstring
113+
Registers the GUI metadata for a new sensor type. Takes ownership of the metadata instance.
114+
%End
115+
116+
117+
QgsAbstractSensor *createSensor( const QString &type, QObject *parent = 0 ) const /TransferBack/;
118+
%Docstring
119+
Creates a new instance of a sensor given the ``type``.
120+
%End
121+
122+
QgsAbstractSensorWidget *createSensorWidget( QgsAbstractSensor *sensor ) const /TransferBack/;
123+
%Docstring
124+
Creates a new instance of a sensor configuration widget for the specified ``sensor``. The
125+
``sensor`` doesn't need to live for the duration of the widget, it is only used when creating
126+
the configuration widget to match a sensor type and initiate the widget to match the
127+
``sensor`` settings.
128+
%End
129+
130+
QMap<QString, QString> sensorTypes() const;
131+
%Docstring
132+
Returns a list of sensor types handled by the registry.
133+
%End
134+
135+
signals:
136+
137+
void sensorAdded( const QString &type, const QString &name );
138+
%Docstring
139+
Emitted whenever a new sensor type is added to the registry, with the specified
140+
``type``.
141+
%End
142+
143+
private:
144+
QgsSensorGuiRegistry( const QgsSensorGuiRegistry &rh );
145+
};
146+
147+
148+
149+
150+
/************************************************************************
151+
* This file has been generated automatically from *
152+
* *
153+
* src/gui/sensor/qgssensorguiregistry.h *
154+
* *
155+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
156+
************************************************************************/
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/************************************************************************
2+
* This file has been generated automatically from *
3+
* *
4+
* src/gui/sensor/qgssensorwidget.h *
5+
* *
6+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
7+
************************************************************************/
8+
9+
10+
11+
12+
13+
class QgsAbstractSensorWidget : QWidget
14+
{
15+
%Docstring(signature="appended")
16+
Base class for widgets which allow control over the properties of sensors.
17+
18+
.. versionadded:: 3.32
19+
%End
20+
21+
%TypeHeaderCode
22+
#include "qgssensorwidget.h"
23+
%End
24+
public:
25+
26+
QgsAbstractSensorWidget( QWidget *parent /TransferThis/ = 0 );
27+
%Docstring
28+
Constructor for QgsAbstractSensorWidget.
29+
30+
:param parent: parent widget
31+
%End
32+
33+
virtual QgsAbstractSensor *createSensor() = 0 /Factory/;
34+
%Docstring
35+
Creates a new sensor matching the settings defined in the widget.
36+
%End
37+
38+
virtual bool updateSensor( QgsAbstractSensor *sensor ) = 0;
39+
%Docstring
40+
Updates an existing ``sensor`` to match the settings defined in the widget. If
41+
``True`` is returned, the ``sensor`` was successfully updated.
42+
43+
If ``False`` is returned, then the widget could not successfully update
44+
the ``sensor``.
45+
%End
46+
47+
virtual bool setSensor( QgsAbstractSensor *sensor ) = 0;
48+
%Docstring
49+
Sets the widget settings to match a given ``sensor``. If ``True`` is returned, ``sensor``
50+
was an acceptable type and the widget has been updated to match
51+
the ``sensor``'s properties.
52+
53+
If ``False`` is returned, then the widget could not be successfully updated
54+
to show the properties of ``sensor``.
55+
%End
56+
57+
signals:
58+
59+
void changed();
60+
%Docstring
61+
Emitted whenever configuration changes happened on this sensor configuration.
62+
%End
63+
};
64+
65+
66+
/************************************************************************
67+
* This file has been generated automatically from *
68+
* *
69+
* src/gui/sensor/qgssensorwidget.h *
70+
* *
71+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
72+
************************************************************************/

‎python/gui/gui_auto.sip

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,8 @@
476476
%Include auto_generated/symbology/qgssymbolslistwidget.sip
477477
%Include auto_generated/symbology/qgssymbolwidgetcontext.sip
478478
%Include auto_generated/symbology/qgsvectorfieldsymbollayerwidget.sip
479+
%Include auto_generated/sensor/qgssensorguiregistry.sip
480+
%Include auto_generated/sensor/qgssensorwidget.sip
479481
%Include auto_generated/settings/qgssettingsregistrygui.sip
480482
%Include auto_generated/tableeditor/qgstableeditordialog.sip
481483
%Include auto_generated/tableeditor/qgstableeditorwidget.sip

‎src/app/sensor/qgsprojectsensorsettingswidget.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,16 @@
2020
#include "qgsapplication.h"
2121
#include "qgsproject.h"
2222
#include "qgssensormanager.h"
23+
#include "qgssensortablewidget.h"
2324

2425
QgsProjectSensorSettingsWidget::QgsProjectSensorSettingsWidget( QWidget *parent )
2526
: QgsOptionsPageWidget( parent )
2627
{
2728
setupUi( this );
2829

30+
QgsSensorTableWidget *widget = new QgsSensorTableWidget( this );
31+
mPanelStack->setMainPanel( widget );
32+
2933
QDomElement sensorElem = QgsProject::instance()->sensorManager()->writeXml( mPreviousSensors );
3034
mPreviousSensors.appendChild( sensorElem );
3135

@@ -67,6 +71,20 @@ void QgsProjectSensorSettingsWidget::cancel()
6771

6872
void QgsProjectSensorSettingsWidget::apply()
6973
{
74+
mPreviousSensors = QDomDocument();
75+
QDomElement sensorElem = QgsProject::instance()->sensorManager()->writeXml( mPreviousSensors );
76+
mPreviousSensors.appendChild( sensorElem );
77+
78+
mConnectedSensors.clear();
79+
const QList<QgsAbstractSensor *> sensors = QgsProject::instance()->sensorManager()->sensors();
80+
for ( QgsAbstractSensor *sensor : sensors )
81+
{
82+
if ( sensor->status() == Qgis::DeviceConnectionStatus::Connected )
83+
{
84+
mConnectedSensors << sensor->id();
85+
}
86+
}
87+
7088
return;
7189
}
7290

‎src/app/sensor/qgssensortablewidget.cpp

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,86 @@
1717

1818
#include "qgisapp.h"
1919
#include "qgsapplication.h"
20+
#include "qgsgui.h"
21+
#include "qgsiodevicesensor.h"
22+
#include "qgssensorguiregistry.h"
2023
#include "qgssensormanager.h"
2124
#include "qgssensormodel.h"
25+
#include "qgssensorwidget.h"
2226
#include "qgsproject.h"
2327

28+
#include <QDialogButtonBox>
2429
#include <QTableWidget>
2530

31+
32+
QgsSensorSettingsWidget::QgsSensorSettingsWidget( QgsAbstractSensor *sensor, QWidget *parent )
33+
: QgsPanelWidget( parent )
34+
, mSensor( sensor )
35+
{
36+
setupUi( this );
37+
setPanelTitle( tr( "Sensor Settings" ) );
38+
setObjectName( QStringLiteral( "SensorSettings" ) );
39+
40+
mNameLineEdit->setText( sensor->name() );
41+
connect( mNameLineEdit, &QLineEdit::textChanged, this, [ = ]() { mButtonBox->button( QDialogButtonBox::Apply )->setEnabled( true ); } );
42+
43+
const QMap<QString, QString> sensorTypes = QgsGui::sensorGuiRegistry()->sensorTypes();
44+
for ( auto sensorIt = sensorTypes.begin(); sensorIt != sensorTypes.end(); ++sensorIt )
45+
{
46+
mTypeComboBox->addItem( QgsGui::sensorGuiRegistry()->sensorMetadata( sensorIt.key() )->creationIcon(), sensorIt.value(), sensorIt.key() );
47+
}
48+
mTypeComboBox->setCurrentIndex( mTypeComboBox->findData( sensor->type() ) );
49+
connect( mTypeComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, [ = ]()
50+
{
51+
mButtonBox->button( QDialogButtonBox::Apply )->setEnabled( true );
52+
setSensorWidget();
53+
} );
54+
55+
mButtonBox->button( QDialogButtonBox::Apply )->setEnabled( false );
56+
connect( mButtonBox->button( QDialogButtonBox::Apply ), &QAbstractButton::clicked, this, [ = ]()
57+
{
58+
apply();
59+
} );
60+
61+
setSensorWidget();
62+
}
63+
64+
void QgsSensorSettingsWidget::setSensorWidget()
65+
{
66+
if ( mSensorWidget )
67+
{
68+
mSensorWidget->deleteLater();
69+
mSensorWidget = nullptr;
70+
}
71+
72+
mSensorWidget = QgsGui::sensorGuiRegistry()->sensorMetadata( mTypeComboBox->currentData().toString() )->createSensorWidget( mSensor );
73+
if ( mSensorWidget )
74+
{
75+
mSensorWidget->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
76+
mSensorWidget->setSensor( mSensor );
77+
mTypeLayout->addWidget( mSensorWidget );
78+
connect( mSensorWidget, &QgsAbstractSensorWidget::changed, this, [ = ]() { mButtonBox->button( QDialogButtonBox::Apply )->setEnabled( true ); } );
79+
}
80+
}
81+
82+
void QgsSensorSettingsWidget::apply()
83+
{
84+
if ( mSensorWidget && mSensor )
85+
{
86+
mSensor->disconnectSensor();
87+
if ( !mSensorWidget->updateSensor( mSensor ) )
88+
{
89+
// The sensor type has changed, remove sensor and add a fresh one
90+
QgsProject::instance()->sensorManager()->removeSensor( mSensor->id() );
91+
mSensor = mSensorWidget->createSensor();
92+
QgsProject::instance()->sensorManager()->addSensor( mSensor );
93+
}
94+
mSensor->setName( mNameLineEdit->text() );
95+
}
96+
}
97+
98+
//----------------
99+
26100
QgsSensorTableWidget::QgsSensorTableWidget( QWidget *parent )
27101
: QgsPanelWidget( parent )
28102
{
@@ -31,6 +105,8 @@ QgsSensorTableWidget::QgsSensorTableWidget( QWidget *parent )
31105
setObjectName( QStringLiteral( "SensorsList" ) );
32106

33107
mActionConnection->setEnabled( false );
108+
mActionRemoveSensor->setEnabled( false );
109+
mActionEditSensor->setEnabled( false );
34110

35111
mSensorModel = new QgsSensorModel( QgsProject::instance()->sensorManager(), this );
36112

@@ -39,6 +115,15 @@ QgsSensorTableWidget::QgsSensorTableWidget( QWidget *parent )
39115
mSensorTable->setSelectionBehavior( QAbstractItemView::SelectRows );
40116
mSensorTable->setSelectionMode( QAbstractItemView::SingleSelection );
41117

118+
connect( mSensorTable, &QAbstractItemView::doubleClicked, this, [ = ]( const QModelIndex & index )
119+
{
120+
if ( index.isValid() )
121+
{
122+
QgsSensorSettingsWidget *settingsWidget = new QgsSensorSettingsWidget( mSensorModel->data( index, QgsSensorModel::Sensor ).value<QgsAbstractSensor *>(), this );
123+
showPanel( settingsWidget );
124+
}
125+
} );
126+
42127
connect( QgsProject::instance()->sensorManager(), &QgsSensorManager::sensorStatusChanged, this, [ = ]( const QString & id )
43128
{
44129
const QModelIndex index = mSensorTable->currentIndex();
@@ -65,6 +150,8 @@ QgsSensorTableWidget::QgsSensorTableWidget( QWidget *parent )
65150
connect( mSensorTable->selectionModel(), &QItemSelectionModel::currentChanged, this, [ = ]( const QModelIndex & current, const QModelIndex & )
66151
{
67152
mActionConnection->setEnabled( current.isValid() );
153+
mActionRemoveSensor->setEnabled( current.isValid() );
154+
mActionEditSensor->setEnabled( current.isValid() );
68155
if ( current.isValid() && mSensorModel->data( current, QgsSensorModel::SensorStatus ).value<Qgis::DeviceConnectionStatus>() == Qgis::DeviceConnectionStatus::Connected )
69156
{
70157
mActionConnection->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionStop.svg" ) ) );
@@ -94,4 +181,33 @@ QgsSensorTableWidget::QgsSensorTableWidget( QWidget *parent )
94181
}
95182
}
96183
} );
184+
185+
connect( mActionAddSensor, &QToolButton::clicked, this, [ = ]()
186+
{
187+
QgsTcpSocketSensor *sensor = new QgsTcpSocketSensor();
188+
sensor->setName( tr( "New sensor" ) );
189+
QgsProject::instance()->sensorManager()->addSensor( sensor );
190+
191+
QgsSensorSettingsWidget *settingsWidget = new QgsSensorSettingsWidget( sensor, this );
192+
showPanel( settingsWidget );
193+
} );
194+
195+
connect( mActionRemoveSensor, &QToolButton::clicked, this, [ = ]()
196+
{
197+
const QModelIndex index = mSensorTable->currentIndex();
198+
if ( index.isValid() )
199+
{
200+
QgsProject::instance()->sensorManager()->removeSensor( mSensorModel->data( index, QgsSensorModel::SensorId ).toString() );
201+
}
202+
} );
203+
204+
connect( mActionEditSensor, &QToolButton::clicked, this, [ = ]()
205+
{
206+
const QModelIndex index = mSensorTable->currentIndex();
207+
if ( index.isValid() )
208+
{
209+
QgsSensorSettingsWidget *settingsWidget = new QgsSensorSettingsWidget( mSensorModel->data( index, QgsSensorModel::Sensor ).value<QgsAbstractSensor *>(), this );
210+
showPanel( settingsWidget );
211+
}
212+
} );
97213
}

‎src/app/sensor/qgssensortablewidget.h

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,33 @@
1717
#define QGSSENSORTABLEWIDGET_H
1818

1919
#include "ui_qgssensortablewidgetbase.h"
20+
#include "ui_qgssensorsettingswidgetbase.h"
2021

2122
#include "qgis_app.h"
2223
#include "qgsdockwidget.h"
24+
#include "qgsabstractsensor.h"
2325
#include "qgspanelwidget.h"
24-
#include "qgspanelwidgetstack.h"
26+
2527

2628
class QgsSensorModel;
29+
class QgsAbstractSensorWidget;
30+
31+
class APP_EXPORT QgsSensorSettingsWidget : public QgsPanelWidget, private Ui::QgsSensorSettingsWidgetBase
32+
{
33+
Q_OBJECT
34+
35+
public:
36+
QgsSensorSettingsWidget( QgsAbstractSensor *sensor = nullptr, QWidget *parent SIP_TRANSFERTHIS = nullptr );
37+
~QgsSensorSettingsWidget() override = default;
38+
39+
void apply();
40+
41+
private:
42+
void setSensorWidget();
43+
44+
QgsAbstractSensor *mSensor = nullptr;
45+
QgsAbstractSensorWidget *mSensorWidget = nullptr;
46+
};
2747

2848
class APP_EXPORT QgsSensorTableWidget : public QgsPanelWidget, private Ui::QgsSensorTableWidgetBase
2949
{

‎src/core/sensor/qgsiodevicesensor.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ bool QgsUdpSocketSensor::readPropertiesFromElement( const QDomElement &element,
274274

275275
//--------------
276276

277+
#if defined( HAVE_QTSERIALPORT )
277278
QgsSerialPortSensor::QgsSerialPortSensor( QObject *parent )
278279
: QgsIODeviceSensor( parent )
279280
, mSerialPort( new QSerialPort() )
@@ -336,3 +337,4 @@ bool QgsSerialPortSensor::readPropertiesFromElement( const QDomElement &element,
336337

337338
return true;
338339
}
340+
#endif

‎src/core/sensor/qgsiodevicesensor.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,21 @@
1616
#ifndef QGSIODEVICESENSOR_H
1717
#define QGSIODEVICESENSOR_H
1818

19+
#include "qgsconfig.h"
20+
1921
#include "qgis_core.h"
2022
#include "qgis_sip.h"
2123
#include "qgsabstractsensor.h"
2224

2325
#include <QBuffer>
2426
#include <QDomElement>
25-
#include <QSerialPort>
2627
#include <QTcpSocket>
2728
#include <QUdpSocket>
2829

30+
#if defined( HAVE_QTSERIALPORT )
31+
#include <QSerialPort>
32+
#endif
33+
2934
/**
3035
* \ingroup core
3136
* \class QgsIODeviceSensor
@@ -212,6 +217,8 @@ class CORE_EXPORT QgsUdpSocketSensor : public QgsIODeviceSensor
212217

213218
};
214219

220+
#if defined( HAVE_QTSERIALPORT )
221+
215222
/**
216223
* \ingroup core
217224
* \class QgsSerialPortSensor
@@ -266,6 +273,7 @@ class CORE_EXPORT QgsSerialPortSensor : public QgsIODeviceSensor
266273
QString mPortName;
267274

268275
};
276+
#endif
269277

270278
#endif //QGSIODEVICESENSOR_H
271279

‎src/core/sensor/qgssensorregistry.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
* *
1515
***************************************************************************/
1616

17+
#include "qgsconfig.h"
18+
1719
#include "qgssensorregistry.h"
1820
#include "qgsiodevicesensor.h"
1921
#include "qgsproject.h"
@@ -36,7 +38,9 @@ bool QgsSensorRegistry::populate()
3638

3739
addSensorType( new QgsSensorMetadata( QLatin1String( "tcp_socket" ), QObject::tr( "TCP socket sensor" ), QgsTcpSocketSensor::create ) );
3840
addSensorType( new QgsSensorMetadata( QLatin1String( "udp_socket" ), QObject::tr( "UDP socket sensor" ), QgsUdpSocketSensor::create ) );
41+
#if defined( HAVE_QTSERIALPORT )
3942
addSensorType( new QgsSensorMetadata( QLatin1String( "serial_port" ), QObject::tr( "Serial port sensor" ), QgsSerialPortSensor::create ) );
43+
#endif
4044

4145
return true;
4246
}

‎src/gui/CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,9 @@ set(QGIS_GUI_SRCS
424424
providers/ogr/qgsogritemguiprovider.cpp
425425
providers/ogr/qgsgeopackageprojectstorageguiprovider.cpp
426426

427+
sensor/qgssensorguiregistry.cpp
428+
sensor/qgssensorwidget.cpp
429+
427430
settings/qgssettingsregistrygui.cpp
428431

429432
tableeditor/qgstableeditordialog.cpp
@@ -1400,6 +1403,9 @@ set(QGIS_GUI_HDRS
14001403
symbology/qgssymbolwidgetcontext.h
14011404
symbology/qgsvectorfieldsymbollayerwidget.h
14021405

1406+
sensor/qgssensorguiregistry.h
1407+
sensor/qgssensorwidget.h
1408+
14031409
settings/qgssettingsregistrygui.h
14041410

14051411
tableeditor/qgstableeditordialog.h
@@ -1547,6 +1553,7 @@ target_include_directories(qgis_gui PUBLIC
15471553
${CMAKE_SOURCE_DIR}/src/gui/providers/ogr
15481554
${CMAKE_SOURCE_DIR}/src/gui/pointcloud
15491555
${CMAKE_SOURCE_DIR}/src/gui/raster
1556+
${CMAKE_SOURCE_DIR}/src/gui/sensor
15501557
${CMAKE_SOURCE_DIR}/src/gui/settings
15511558
${CMAKE_SOURCE_DIR}/src/gui/tableeditor
15521559
${CMAKE_SOURCE_DIR}/src/gui/vector

‎src/gui/qgsgui.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
#include "qgssettingsregistrygui.h"
6161
#include "qgshistoryproviderregistry.h"
6262
#include "qgslayermetadatasourceselectprovider.h"
63+
#include "qgssensorguiregistry.h"
6364

6465
#include <QPushButton>
6566
#include <QToolButton>
@@ -170,6 +171,11 @@ QgsProviderGuiRegistry *QgsGui::providerGuiRegistry()
170171
return instance()->mProviderGuiRegistry;
171172
}
172173

174+
QgsSensorGuiRegistry *QgsGui::sensorGuiRegistry()
175+
{
176+
return instance()->mSensorGuiRegistry;
177+
}
178+
173179
QgsHistoryProviderRegistry *QgsGui::historyProviderRegistry()
174180
{
175181
return instance()->mHistoryProviderRegistry;
@@ -230,6 +236,7 @@ QgsGui::~QgsGui()
230236
delete mShapeMapToolRegistry;
231237
delete mRelationEditorRegistry;
232238
delete mSettingsRegistryGui;
239+
delete mSensorGuiRegistry;
233240
}
234241

235242
QColor QgsGui::sampleColor( QPoint point )
@@ -283,6 +290,9 @@ QgsGui::QgsGui()
283290
mCodeEditorColorSchemeRegistry = new QgsCodeEditorColorSchemeRegistry();
284291

285292
// provider gui registry initialize QgsProviderRegistry too
293+
mSensorGuiRegistry = new QgsSensorGuiRegistry();
294+
mSensorGuiRegistry->populate();
295+
286296
mHistoryProviderRegistry = new QgsHistoryProviderRegistry();
287297
mHistoryProviderRegistry->addDefaultProviders();
288298

‎src/gui/qgsgui.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ class QgsProviderSourceWidgetProviderRegistry;
4747
class QgsRelationWidgetRegistry;
4848
class QgsMapToolShapeRegistry;
4949
class QgsHistoryProviderRegistry;
50+
class QgsSensorGuiRegistry;
51+
5052

5153
/**
5254
* \ingroup gui
@@ -174,6 +176,12 @@ class GUI_EXPORT QgsGui : public QObject
174176
*/
175177
static QgsProviderGuiRegistry *providerGuiRegistry() SIP_KEEPREFERENCE;
176178

179+
/**
180+
* Returns the registry of GUI-related components for sensors
181+
* \since QGIS 3.32
182+
*/
183+
static QgsSensorGuiRegistry *sensorGuiRegistry() SIP_KEEPREFERENCE;
184+
177185
/**
178186
* Returns the registry of subset string editors of data providers
179187
* \since QGIS 3.18
@@ -313,6 +321,7 @@ class GUI_EXPORT QgsGui : public QObject
313321
QgsRelationWidgetRegistry *mRelationEditorRegistry = nullptr;
314322
QgsMapToolShapeRegistry *mShapeMapToolRegistry = nullptr;
315323
QgsHistoryProviderRegistry *mHistoryProviderRegistry = nullptr;
324+
QgsSensorGuiRegistry *mSensorGuiRegistry = nullptr;
316325
std::unique_ptr< QgsWindowManagerInterface > mWindowManager;
317326

318327
#ifdef SIP_RUN
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/***************************************************************************
2+
qgssensorguiregistry.h
3+
--------------------------
4+
begin : March 2023
5+
copyright : (C) 2023 by Mathieu Pellerin
6+
email : mathieu at opengis dot ch
7+
***************************************************************************/
8+
/***************************************************************************
9+
* *
10+
* This program is free software; you can redistribute it and/or modify *
11+
* it under the terms of the GNU General Public License as published by *
12+
* the Free Software Foundation; either version 2 of the License, or *
13+
* (at your option) any later version. *
14+
* *
15+
***************************************************************************/
16+
17+
#include "qgsconfig.h"
18+
19+
#include "qgssensorguiregistry.h"
20+
#include "qgssensorwidget.h"
21+
22+
QgsSensorGuiRegistry::QgsSensorGuiRegistry( QObject *parent )
23+
: QObject( parent )
24+
{
25+
}
26+
27+
QgsSensorGuiRegistry::~QgsSensorGuiRegistry()
28+
{
29+
qDeleteAll( mMetadata );
30+
}
31+
32+
bool QgsSensorGuiRegistry::populate()
33+
{
34+
if ( !mMetadata.isEmpty() )
35+
return false;
36+
37+
addSensorGuiMetadata( new QgsSensorGuiMetadata( QStringLiteral( "tcp_socket" ),
38+
QObject::tr( "TCP socket sensor" ),
39+
QgsApplication::getThemeIcon( QStringLiteral( "/mSensor.svg" ) ),
40+
[ = ]( QgsAbstractSensor * sensor )->QgsAbstractSensorWidget *
41+
{
42+
QgsTcpSocketSensorWidget *widget = new QgsTcpSocketSensorWidget( nullptr );
43+
widget->setSensor( sensor );
44+
return widget;
45+
}, nullptr ) );
46+
addSensorGuiMetadata( new QgsSensorGuiMetadata( QStringLiteral( "udp_socket" ),
47+
QObject::tr( "UDP socket sensor" ),
48+
QgsApplication::getThemeIcon( QStringLiteral( "/mSensor.svg" ) ),
49+
[ = ]( QgsAbstractSensor * sensor )->QgsAbstractSensorWidget *
50+
{
51+
QgsUdpSocketSensorWidget *widget = new QgsUdpSocketSensorWidget( nullptr );
52+
widget->setSensor( sensor );
53+
return widget;
54+
}, nullptr ) );
55+
#if defined( HAVE_QTSERIALPORT )
56+
addSensorGuiMetadata( new QgsSensorGuiMetadata( QStringLiteral( "serial_port" ),
57+
QObject::tr( "Serial port sensor" ),
58+
QgsApplication::getThemeIcon( QStringLiteral( "/mSensor.svg" ) ),
59+
[ = ]( QgsAbstractSensor * sensor )->QgsAbstractSensorWidget *
60+
{
61+
QgsSerialPortSensorWidget *widget = new QgsSerialPortSensorWidget( nullptr );
62+
widget->setSensor( sensor );
63+
return widget;
64+
}, nullptr ) );
65+
#endif
66+
return true;
67+
}
68+
69+
QgsSensorAbstractGuiMetadata *QgsSensorGuiRegistry::sensorMetadata( const QString &type ) const
70+
{
71+
return mMetadata.value( type );
72+
}
73+
74+
bool QgsSensorGuiRegistry::addSensorGuiMetadata( QgsSensorAbstractGuiMetadata *metadata )
75+
{
76+
if ( !metadata || mMetadata.contains( metadata->type() ) )
77+
return false;
78+
79+
mMetadata[metadata->type()] = metadata;
80+
emit sensorAdded( metadata->type(), metadata->visibleName() );
81+
return true;
82+
}
83+
84+
QgsAbstractSensor *QgsSensorGuiRegistry::createSensor( const QString &type, QObject *parent ) const
85+
{
86+
if ( !mMetadata.contains( type ) )
87+
return nullptr;
88+
89+
std::unique_ptr< QgsAbstractSensor > sensor( mMetadata.value( type )->createSensor( parent ) );
90+
if ( sensor )
91+
return sensor.release();
92+
93+
return QgsApplication::sensorRegistry()->createSensor( type, parent );
94+
}
95+
96+
QgsAbstractSensorWidget *QgsSensorGuiRegistry::createSensorWidget( QgsAbstractSensor *sensor ) const
97+
{
98+
if ( !sensor || !mMetadata.contains( sensor->type() ) )
99+
return nullptr;
100+
101+
return mMetadata[sensor->type()]->createSensorWidget( sensor );
102+
}
103+
104+
QMap<QString, QString> QgsSensorGuiRegistry::sensorTypes() const
105+
{
106+
QMap<QString, QString> types;
107+
for ( auto it = mMetadata.constBegin(); it != mMetadata.constEnd(); ++it )
108+
{
109+
types.insert( it.key(), it.value()->visibleName() );
110+
}
111+
112+
return types;
113+
}

‎src/gui/sensor/qgssensorguiregistry.h

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
/***************************************************************************
2+
qgssensorguiregistry.h
3+
--------------------------
4+
begin : March 2023
5+
copyright : (C) 2023 by Mathieu Pellerin
6+
email : mathieu at opengis dot ch
7+
***************************************************************************/
8+
/***************************************************************************
9+
* *
10+
* This program is free software; you can redistribute it and/or modify *
11+
* it under the terms of the GNU General Public License as published by *
12+
* the Free Software Foundation; either version 2 of the License, or *
13+
* (at your option) any later version. *
14+
* *
15+
***************************************************************************/
16+
#ifndef QGSSENSORGUIREGISTRY_H
17+
#define QGSSENSORGUIREGISTRY_H
18+
19+
#include "qgis_gui.h"
20+
#include "qgis_sip.h"
21+
#include "qgsabstractsensor.h"
22+
#include "qgssensorregistry.h"
23+
#include "qgssensorwidget.h"
24+
25+
#include <QIcon>
26+
27+
/**
28+
* \ingroup gui
29+
* \brief Stores GUI metadata about one sensor class.
30+
*
31+
* This is a companion to QgsSensorAbstractMetadata, storing only
32+
* the components related to the GUI behavior of sensor.
33+
*
34+
* \note In C++ you can use QgsSensorGuiMetadata convenience class.
35+
* \since QGIS 3.32
36+
*/
37+
class GUI_EXPORT QgsSensorAbstractGuiMetadata
38+
{
39+
public:
40+
41+
/**
42+
* Constructor for QgsSensorAbstractGuiMetadata with the specified class \a type.
43+
*
44+
* \a visibleName should be set to a translated, user visible name identifying the corresponding sensor type.
45+
*/
46+
QgsSensorAbstractGuiMetadata( const QString &type, const QString &visibleName )
47+
: mType( type )
48+
, mVisibleName( visibleName )
49+
{}
50+
51+
virtual ~QgsSensorAbstractGuiMetadata() = default;
52+
53+
/**
54+
* Returns the unique type code for the sensor class.
55+
*/
56+
QString type() const { return mType; }
57+
58+
/**
59+
* Returns a translated, user visible name identifying the corresponding sensor.
60+
*/
61+
QString visibleName() const { return mVisibleName; }
62+
63+
/**
64+
* Returns an icon representing creation of the sensor type.
65+
*/
66+
virtual QIcon creationIcon() const { return QgsApplication::getThemeIcon( QStringLiteral( "/mSensor.svg" ) ); }
67+
68+
/*
69+
* IMPORTANT: While it seems like /Factory/ would be the correct annotations here, that's not
70+
* the case.
71+
* As per Phil Thomson's advice on https://www.riverbankcomputing.com/pipermail/pyqt/2017-July/039450.html:
72+
*
73+
* "
74+
* /Factory/ is used when the instance returned is guaranteed to be new to Python.
75+
* In this case it isn't because it has already been seen when being returned by QgsProcessingAlgorithm::createInstance()
76+
* (However for a different sub-class implemented in C++ then it would be the first time it was seen
77+
* by Python so the /Factory/ on create() would be correct.)
78+
*
79+
* You might try using /TransferBack/ on create() instead - that might be the best compromise.
80+
* "
81+
*/
82+
83+
/**
84+
* Creates a configuration widget for an \a sensor of this type. Can return NULLPTR if no configuration GUI is required.
85+
*/
86+
virtual QgsAbstractSensorWidget *createSensorWidget( QgsAbstractSensor *sensor ) SIP_TRANSFERBACK { Q_UNUSED( sensor ) return nullptr; }
87+
88+
/**
89+
* Creates an instance of the corresponding sensor type.
90+
*/
91+
virtual QgsAbstractSensor *createSensor( QObject *parent ) SIP_TRANSFERBACK { Q_UNUSED( parent ) return nullptr; }
92+
93+
private:
94+
95+
QString mType;
96+
QString mVisibleName;
97+
98+
};
99+
100+
//! Sensor configuration widget creation function
101+
typedef std::function<QgsAbstractSensorWidget *( QgsAbstractSensor *sensor )> QgsSensorWidgetFunc SIP_SKIP;
102+
103+
#ifndef SIP_RUN
104+
105+
/**
106+
* \ingroup gui
107+
* \brief Convenience metadata class that uses static functions to handle sensor GUI behavior.
108+
* \note not available in Python bindings
109+
* \since QGIS 3.0
110+
*/
111+
class GUI_EXPORT QgsSensorGuiMetadata : public QgsSensorAbstractGuiMetadata
112+
{
113+
public:
114+
115+
/**
116+
* Constructor for QgsSensorGuiMetadata with the specified class \a type
117+
* and \a creationIcon, and function pointers for the configuration widget creation function.
118+
*
119+
* \a visibleName should be set to a translated, user visible name identifying the corresponding sensor.
120+
*/
121+
QgsSensorGuiMetadata( const QString &type, const QString &visibleName, const QIcon &creationIcon,
122+
const QgsSensorWidgetFunc &pfWidget = nullptr,
123+
const QgsSensorCreateFunc &pfCreateFunc = nullptr )
124+
: QgsSensorAbstractGuiMetadata( type, visibleName )
125+
, mIcon( creationIcon )
126+
, mWidgetFunc( pfWidget )
127+
, mCreateFunc( pfCreateFunc )
128+
{}
129+
130+
/**
131+
* Returns the classes' configuration widget creation function.
132+
* \see setWidgetFunction()
133+
*/
134+
QgsSensorWidgetFunc widgetFunction() const { return mWidgetFunc; }
135+
136+
/**
137+
* Sets the classes' sensor configuration widget creation \a function.
138+
* \see widgetFunction()
139+
*/
140+
void setWidgetFunction( const QgsSensorWidgetFunc &function ) { mWidgetFunc = function; }
141+
142+
/**
143+
* Returns the classes' sensor creation function.
144+
* \see setSensorCreationFunction()
145+
*/
146+
QgsSensorCreateFunc sensorCreationFunction() const { return mCreateFunc; }
147+
148+
/**
149+
* Sets the classes' sensor creation \a function.
150+
* \see sensorCreationFunction()
151+
*/
152+
void setSensorCreationFunction( const QgsSensorCreateFunc &function ) { mCreateFunc = function; }
153+
154+
QIcon creationIcon() const override { return mIcon.isNull() ? QgsSensorAbstractGuiMetadata::creationIcon() : mIcon; }
155+
QgsAbstractSensorWidget *createSensorWidget( QgsAbstractSensor *sensor ) override { return mWidgetFunc ? mWidgetFunc( sensor ) : nullptr; }
156+
QgsAbstractSensor *createSensor( QObject *parent ) override { return mCreateFunc ? mCreateFunc( parent ) : nullptr; }
157+
158+
protected:
159+
160+
QIcon mIcon;
161+
QgsSensorWidgetFunc mWidgetFunc = nullptr;
162+
QgsSensorCreateFunc mCreateFunc = nullptr;
163+
164+
};
165+
166+
#endif
167+
168+
/**
169+
* \ingroup gui
170+
* \class QgsSensorGuiRegistry
171+
* \brief Registry of available sensor GUI behavior.
172+
*
173+
* QgsSensorGuiRegistry is not usually directly created, but rather accessed through
174+
* QgsGui::sensorGuiRegistry().
175+
*
176+
* This acts as a companion to QgsSensorRegistry, handling only
177+
* the components related to the GUI behavior of sensors.
178+
*
179+
* \since QGIS 3.32
180+
*/
181+
class GUI_EXPORT QgsSensorGuiRegistry : public QObject
182+
{
183+
Q_OBJECT
184+
185+
public:
186+
187+
/**
188+
* Creates a new empty sensor GUI registry.
189+
*
190+
* QgsSensorGuiRegistry is not usually directly created, but rather accessed through
191+
* QgsGui::sensorGuiRegistry().
192+
*/
193+
QgsSensorGuiRegistry( QObject *parent = nullptr );
194+
~QgsSensorGuiRegistry() override;
195+
196+
//! QgsSensorGuiRegistry cannot be copied.
197+
QgsSensorGuiRegistry( const QgsSensorGuiRegistry &rh ) = delete;
198+
//! QgsSensorGuiRegistry cannot be copied.
199+
QgsSensorGuiRegistry &operator=( const QgsSensorGuiRegistry &rh ) = delete;
200+
201+
/**
202+
* Populates the registry with standard sensor types. If called on a non-empty registry
203+
* then this will have no effect and will return FALSE.
204+
*/
205+
bool populate();
206+
207+
/**
208+
* Returns the metadata for the specified sensor \a type. Returns NULLPTR if
209+
* a corresponding sensor type was not found in the registry.
210+
*/
211+
QgsSensorAbstractGuiMetadata *sensorMetadata( const QString &type ) const;
212+
213+
/**
214+
* Registers the GUI metadata for a new sensor type. Takes ownership of the metadata instance.
215+
*/
216+
bool addSensorGuiMetadata( QgsSensorAbstractGuiMetadata *metadata SIP_TRANSFER );
217+
218+
/*
219+
* IMPORTANT: While it seems like /Factory/ would be the correct annotations here, that's not
220+
* the case.
221+
* As per Phil Thomson's advice on https://www.riverbankcomputing.com/pipermail/pyqt/2017-July/039450.html:
222+
*
223+
* "
224+
* /Factory/ is used when the instance returned is guaranteed to be new to Python.
225+
* In this case it isn't because it has already been seen when being returned by QgsProcessingAlgorithm::createInstance()
226+
* (However for a different sub-class implemented in C++ then it would be the first time it was seen
227+
* by Python so the /Factory/ on create() would be correct.)
228+
*
229+
* You might try using /TransferBack/ on create() instead - that might be the best compromise.
230+
* "
231+
*/
232+
233+
/**
234+
* Creates a new instance of a sensor given the \a type.
235+
*/
236+
QgsAbstractSensor *createSensor( const QString &type, QObject *parent = nullptr ) const SIP_TRANSFERBACK;
237+
238+
/**
239+
* Creates a new instance of a sensor configuration widget for the specified \a sensor. The
240+
* \a sensor doesn't need to live for the duration of the widget, it is only used when creating
241+
* the configuration widget to match a sensor type and initiate the widget to match the
242+
* \a sensor settings.
243+
*/
244+
QgsAbstractSensorWidget *createSensorWidget( QgsAbstractSensor *sensor ) const SIP_TRANSFERBACK;
245+
246+
/**
247+
* Returns a list of sensor types handled by the registry.
248+
*/
249+
QMap<QString, QString> sensorTypes() const;
250+
251+
signals:
252+
253+
/**
254+
* Emitted whenever a new sensor type is added to the registry, with the specified
255+
* \a type.
256+
*/
257+
void sensorAdded( const QString &type, const QString &name );
258+
259+
private:
260+
261+
#ifdef SIP_RUN
262+
QgsSensorGuiRegistry( const QgsSensorGuiRegistry &rh );
263+
#endif
264+
265+
QMap<QString, QgsSensorAbstractGuiMetadata *> mMetadata;
266+
267+
};
268+
269+
#endif //QGSSENSORGUIREGISTRY_H
270+
271+
272+

‎src/gui/sensor/qgssensorwidget.cpp

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
/***************************************************************************
2+
qgssensorwidget.cpp
3+
---------------------
4+
begin : March 2023
5+
copyright : (C) 2023 by Mathieu Pellerin
6+
email : mathieu at opengis dot ch
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#include "qgsconfig.h"
17+
18+
#include "qgssensorwidget.h"
19+
#include "qgsiodevicesensor.h"
20+
21+
#if defined( HAVE_QTSERIALPORT )
22+
#include <QSerialPort>
23+
#include <QSerialPortInfo>
24+
#endif
25+
26+
QgsAbstractSensorWidget::QgsAbstractSensorWidget( QWidget *parent SIP_TRANSFERTHIS )
27+
: QWidget( parent )
28+
{
29+
}
30+
31+
// ------------------------
32+
33+
///@cond PRIVATE
34+
35+
QgsTcpSocketSensorWidget::QgsTcpSocketSensorWidget( QWidget *parent )
36+
: QgsAbstractSensorWidget( parent )
37+
{
38+
setupUi( this );
39+
40+
connect( mHostNameLineEdit, &QLineEdit::textChanged, this, &QgsAbstractSensorWidget::changed );
41+
connect( mPortSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsAbstractSensorWidget::changed );
42+
}
43+
44+
QgsAbstractSensor *QgsTcpSocketSensorWidget::createSensor()
45+
{
46+
QgsTcpSocketSensor *s = new QgsTcpSocketSensor();
47+
s->setHostName( mHostNameLineEdit->text() );
48+
s->setPort( mPortSpinBox->value() );
49+
return s;
50+
}
51+
52+
bool QgsTcpSocketSensorWidget::updateSensor( QgsAbstractSensor *sensor )
53+
{
54+
QgsTcpSocketSensor *s = dynamic_cast<QgsTcpSocketSensor *>( sensor );
55+
if ( !s )
56+
return false;
57+
58+
s->setHostName( mHostNameLineEdit->text() );
59+
s->setPort( mPortSpinBox->value() );
60+
61+
return true;
62+
}
63+
64+
bool QgsTcpSocketSensorWidget::setSensor( QgsAbstractSensor *sensor )
65+
{
66+
QgsTcpSocketSensor *ts = dynamic_cast<QgsTcpSocketSensor *>( sensor );
67+
if ( ts )
68+
{
69+
mHostNameLineEdit->setText( ts->hostName() );
70+
mPortSpinBox->setValue( ts->port() );
71+
return true;
72+
}
73+
74+
QgsUdpSocketSensor *us = dynamic_cast<QgsUdpSocketSensor *>( sensor );
75+
if ( us )
76+
{
77+
mHostNameLineEdit->setText( us->hostName() );
78+
mPortSpinBox->setValue( us->port() );
79+
return true;
80+
}
81+
82+
return false;
83+
}
84+
85+
// ------------------------
86+
87+
QgsUdpSocketSensorWidget::QgsUdpSocketSensorWidget( QWidget *parent )
88+
: QgsAbstractSensorWidget( parent )
89+
{
90+
setupUi( this );
91+
92+
connect( mHostNameLineEdit, &QLineEdit::textChanged, this, &QgsAbstractSensorWidget::changed );
93+
connect( mPortSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsAbstractSensorWidget::changed );
94+
}
95+
96+
QgsAbstractSensor *QgsUdpSocketSensorWidget::createSensor()
97+
{
98+
QgsUdpSocketSensor *s = new QgsUdpSocketSensor();
99+
s->setHostName( mHostNameLineEdit->text() );
100+
s->setPort( mPortSpinBox->value() );
101+
return s;
102+
}
103+
104+
bool QgsUdpSocketSensorWidget::updateSensor( QgsAbstractSensor *sensor )
105+
{
106+
QgsUdpSocketSensor *s = dynamic_cast<QgsUdpSocketSensor *>( sensor );
107+
if ( !s )
108+
return false;
109+
110+
s->setHostName( mHostNameLineEdit->text() );
111+
s->setPort( mPortSpinBox->value() );
112+
113+
return true;
114+
}
115+
116+
bool QgsUdpSocketSensorWidget::setSensor( QgsAbstractSensor *sensor )
117+
{
118+
QgsTcpSocketSensor *ts = dynamic_cast<QgsTcpSocketSensor *>( sensor );
119+
if ( ts )
120+
{
121+
mHostNameLineEdit->setText( ts->hostName() );
122+
mPortSpinBox->setValue( ts->port() );
123+
return true;
124+
}
125+
126+
QgsUdpSocketSensor *us = dynamic_cast<QgsUdpSocketSensor *>( sensor );
127+
if ( us )
128+
{
129+
mHostNameLineEdit->setText( us->hostName() );
130+
mPortSpinBox->setValue( us->port() );
131+
return true;
132+
}
133+
134+
return false;
135+
}
136+
137+
// ------------------------
138+
139+
#if defined( HAVE_QTSERIALPORT )
140+
QgsSerialPortSensorWidget::QgsSerialPortSensorWidget( QWidget *parent )
141+
: QgsAbstractSensorWidget( parent )
142+
{
143+
setupUi( this );
144+
145+
for ( const QSerialPortInfo &info : QSerialPortInfo::availablePorts() )
146+
{
147+
mSerialPortComboBox->addItem( QStringLiteral( "%1: %2" ).arg( info.portName(), info.description() ), info.portName() );
148+
}
149+
150+
updateSerialPortDetails();
151+
152+
connect( mSerialPortComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, [ = ]()
153+
{
154+
updateSerialPortDetails();
155+
emit changed();
156+
} );
157+
}
158+
159+
QgsAbstractSensor *QgsSerialPortSensorWidget::createSensor()
160+
{
161+
QgsSerialPortSensor *s = new QgsSerialPortSensor();
162+
s->setPortName( mSerialPortComboBox->currentData().toString() );
163+
return s;
164+
}
165+
166+
bool QgsSerialPortSensorWidget::updateSensor( QgsAbstractSensor *sensor )
167+
{
168+
QgsSerialPortSensor *s = dynamic_cast<QgsSerialPortSensor *>( sensor );
169+
if ( !s )
170+
return false;
171+
172+
s->setPortName( mSerialPortComboBox->currentData().toString() );
173+
174+
return true;
175+
}
176+
177+
bool QgsSerialPortSensorWidget::setSensor( QgsAbstractSensor *sensor )
178+
{
179+
QgsSerialPortSensor *s = dynamic_cast<QgsSerialPortSensor *>( sensor );
180+
if ( !s )
181+
return false;
182+
183+
const int index = mSerialPortComboBox->findData( s->portName() );
184+
if ( index >= 0 )
185+
{
186+
mSerialPortComboBox->setCurrentIndex( index );
187+
}
188+
else
189+
{
190+
mSerialPortComboBox->addItem( s->portName(), s->portName() );
191+
mSerialPortComboBox->setCurrentIndex( mSerialPortComboBox->count() - 1 );
192+
}
193+
194+
return true;
195+
}
196+
197+
void QgsSerialPortSensorWidget::updateSerialPortDetails()
198+
{
199+
if ( mSerialPortComboBox->currentIndex() < 0 )
200+
{
201+
return;
202+
}
203+
204+
const QString &currentPortName = mSerialPortComboBox->currentData().toString();
205+
bool serialPortFound = false;
206+
for ( const QSerialPortInfo &info : QSerialPortInfo::availablePorts() )
207+
{
208+
serialPortFound = info.portName() == currentPortName;
209+
if ( serialPortFound )
210+
{
211+
mSerialPortDetails->setText( QStringLiteral( "%1:\n- %2: %3\n- %4: %5\n- %6: %7\n- %8: %9\n- %10: %11" ).arg( tr( "Serial port details" ),
212+
tr( "Port name" ), info.portName(),
213+
tr( "Description" ), info.description(),
214+
tr( "Manufacturer" ), info.manufacturer(),
215+
tr( "Product identifier" ), QString::number( info.productIdentifier() ),
216+
tr( "Serial number" ), info.serialNumber() ) );
217+
break;
218+
}
219+
}
220+
if ( !serialPortFound )
221+
{
222+
mSerialPortDetails->setText( QStringLiteral( "%1:\n- %2: %3" ).arg( tr( "Serial port details" ),
223+
tr( "Port name" ), currentPortName ) );
224+
}
225+
}
226+
#endif
227+
228+
///@endcond

‎src/gui/sensor/qgssensorwidget.h

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/***************************************************************************
2+
qgssensorwidget.h
3+
---------------------
4+
begin : March 2023
5+
copyright : (C) 2023 by Mathieu Pellerin
6+
email : mathieu at opengis dot ch
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
#ifndef QGSSENSORWIDGET_H
16+
#define QGSSENSORWIDGET_H
17+
18+
#include "ui_widget_tcpsocketsensor.h"
19+
#include "ui_widget_udpsocketsensor.h"
20+
#include "ui_widget_serialportsensor.h"
21+
22+
#include "qgsconfig.h"
23+
24+
#include "qgis_sip.h"
25+
#include "qgis_gui.h"
26+
#include "qgsabstractsensor.h"
27+
28+
#include <QWidget>
29+
30+
/**
31+
* \ingroup gui
32+
* \class QgsAbstractSensorWidget
33+
* \brief Base class for widgets which allow control over the properties of sensors.
34+
* \since QGIS 3.32
35+
*/
36+
class GUI_EXPORT QgsAbstractSensorWidget : public QWidget
37+
{
38+
39+
Q_OBJECT
40+
41+
public:
42+
43+
/**
44+
* Constructor for QgsAbstractSensorWidget.
45+
* \param parent parent widget
46+
*/
47+
QgsAbstractSensorWidget( QWidget *parent SIP_TRANSFERTHIS = nullptr );
48+
49+
/**
50+
* Creates a new sensor matching the settings defined in the widget.
51+
*/
52+
virtual QgsAbstractSensor *createSensor() = 0 SIP_FACTORY;
53+
54+
/**
55+
* Updates an existing \a sensor to match the settings defined in the widget. If
56+
* TRUE is returned, the \a sensor was successfully updated.
57+
*
58+
* If FALSE is returned, then the widget could not successfully update
59+
* the \a sensor.
60+
*/
61+
virtual bool updateSensor( QgsAbstractSensor *sensor ) = 0;
62+
63+
/**
64+
* Sets the widget settings to match a given \a sensor. If TRUE is returned, \a sensor
65+
* was an acceptable type and the widget has been updated to match
66+
* the \a sensor's properties.
67+
*
68+
* If FALSE is returned, then the widget could not be successfully updated
69+
* to show the properties of \a sensor.
70+
*/
71+
virtual bool setSensor( QgsAbstractSensor *sensor ) = 0;
72+
73+
signals:
74+
75+
/**
76+
* Emitted whenever configuration changes happened on this sensor configuration.
77+
*/
78+
void changed();
79+
};
80+
81+
#ifndef SIP_RUN
82+
///@cond PRIVATE
83+
84+
/**
85+
* \ingroup gui
86+
* \class QgsTcpSocketSensorWidget
87+
* \brief A configuration widget which allow control over QgsTcpSocketSensor properties.
88+
* \since QGIS 3.32
89+
*/
90+
class GUI_EXPORT QgsTcpSocketSensorWidget : public QgsAbstractSensorWidget, private Ui::WidgetTcpSocketSensor
91+
{
92+
93+
Q_OBJECT
94+
95+
public:
96+
97+
/**
98+
* Constructor for QgsTcpSocketSensorWidget.
99+
* \param parent parent widget
100+
*/
101+
QgsTcpSocketSensorWidget( QWidget *parent );
102+
103+
QgsAbstractSensor *createSensor() override;
104+
bool updateSensor( QgsAbstractSensor *sensor ) override;
105+
bool setSensor( QgsAbstractSensor *sensor ) override;
106+
};
107+
108+
/**
109+
* \ingroup gui
110+
* \class QgsUdpSocketSensorWidget
111+
* \brief A configuration widget which allow control over QgsUdpSocketSensorWidget properties.
112+
* \since QGIS 3.32
113+
*/
114+
class GUI_EXPORT QgsUdpSocketSensorWidget : public QgsAbstractSensorWidget, private Ui::WidgetUdpSocketSensor
115+
{
116+
117+
Q_OBJECT
118+
119+
public:
120+
121+
/**
122+
* Constructor for QgsUdpSocketSensorWidget.
123+
* \param parent parent widget
124+
*/
125+
QgsUdpSocketSensorWidget( QWidget *parent );
126+
127+
QgsAbstractSensor *createSensor() override;
128+
bool updateSensor( QgsAbstractSensor *sensor ) override;
129+
bool setSensor( QgsAbstractSensor *sensor ) override;
130+
};
131+
132+
#if defined( HAVE_QTSERIALPORT )
133+
134+
/**
135+
* \ingroup gui
136+
* \class QgsSerialPortSensorWidget
137+
* \brief A configuration widget which allow control over QgsSerialPortSensor properties.
138+
* \since QGIS 3.32
139+
*/
140+
class GUI_EXPORT QgsSerialPortSensorWidget : public QgsAbstractSensorWidget, private Ui::WidgetSerialPortSensor
141+
{
142+
143+
Q_OBJECT
144+
145+
public:
146+
147+
/**
148+
* Constructor for QgsSerialPortSensorWidget.
149+
* \param parent parent widget
150+
*/
151+
QgsSerialPortSensorWidget( QWidget *parent );
152+
153+
QgsAbstractSensor *createSensor() override;
154+
bool updateSensor( QgsAbstractSensor *sensor ) override;
155+
bool setSensor( QgsAbstractSensor *sensor ) override;
156+
157+
private:
158+
159+
void updateSerialPortDetails();
160+
};
161+
#endif
162+
163+
#endif
164+
///@endcond
165+
166+
#endif // QGSSENSORWIDGET_H

‎src/ui/qgisapp.ui

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2854,6 +2854,21 @@ Shift+click to snap rotation to 45 degree steps.</string>
28542854
<string>Show Statistical Summary</string>
28552855
</property>
28562856
</action>
2857+
<action name="mActionSensorDock">
2858+
<property name="checkable">
2859+
<bool>true</bool>
2860+
</property>
2861+
<property name="icon">
2862+
<iconset resource="../../images/images.qrc">
2863+
<normaloff>:/images/themes/default/mSensorDock.svg</normaloff>:/images/themes/default/mSensorDock.svg</iconset>
2864+
</property>
2865+
<property name="text">
2866+
<string>Sensors</string>
2867+
</property>
2868+
<property name="toolTip">
2869+
<string>Show Sensors</string>
2870+
</property>
2871+
</action>
28572872
<action name="mActionShowAlignRasterTool">
28582873
<property name="text">
28592874
<string>Align Rasters…</string>

‎src/ui/qgsprojectpropertiesbase.ui

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3373,7 +3373,7 @@
33733373
</spacer>
33743374
</item>
33753375
</layout>
3376-
</widget>
3376+
</widget>
33773377
</widget>
33783378
</item>
33793379
</layout>

‎src/ui/sensor/qgsprojectsensorettingswidgetbase.ui

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@
2929
<item>
3030
<widget class="QGroupBox" name="groupBox">
3131
<property name="title">
32-
<string>Sensor Settings</string>
32+
<string>Sensors</string>
3333
</property>
3434
<layout class="QVBoxLayout" name="mainLayout">
3535
<item>
36-
<widget class="QgsSensorTableWidget" name="mSensorTableWidget"/>
36+
<widget class="QgsPanelWidgetStack" name="mPanelStack" native="true"/>
3737
</item>
3838
</layout>
3939
</widget>
@@ -42,9 +42,9 @@
4242
</widget>
4343
<customwidgets>
4444
<customwidget>
45-
<class>QgsSensorTableWidget</class>
45+
<class>QgsPanelWidgetStack</class>
4646
<extends>QWidget</extends>
47-
<header>qgssensortablewidget.h</header>
47+
<header>qgspanelwidgetstack.h</header>
4848
<container>1</container>
4949
</customwidget>
5050
</customwidgets>
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ui version="4.0">
3+
<class>QgsSensorSettingsWidgetBase</class>
4+
<widget class="QgsPanelWidget" name="QgsSensorSettingsWidgetBase">
5+
<property name="geometry">
6+
<rect>
7+
<x>0</x>
8+
<y>0</y>
9+
<width>291</width>
10+
<height>376</height>
11+
</rect>
12+
</property>
13+
<layout class="QVBoxLayout" name="mMainLayout">
14+
<property name="spacing">
15+
<number>3</number>
16+
</property>
17+
<property name="leftMargin">
18+
<number>0</number>
19+
</property>
20+
<property name="topMargin">
21+
<number>0</number>
22+
</property>
23+
<property name="rightMargin">
24+
<number>0</number>
25+
</property>
26+
<property name="bottomMargin">
27+
<number>0</number>
28+
</property>
29+
<item>
30+
<widget class="QLabel" name="mNameLabel">
31+
<property name="text">
32+
<string>Sensor name</string>
33+
</property>
34+
<property name="wordWrap">
35+
<bool>true</bool>
36+
</property>
37+
</widget>
38+
</item>
39+
<item>
40+
<widget class="QLineEdit" name="mNameLineEdit"/>
41+
</item>
42+
<item>
43+
<widget class="QLabel" name="mTypeLabel">
44+
<property name="text">
45+
<string>Sensor type</string>
46+
</property>
47+
<property name="wordWrap">
48+
<bool>true</bool>
49+
</property>
50+
</widget>
51+
</item>
52+
<item>
53+
<widget class="QComboBox" name="mTypeComboBox"/>
54+
</item>
55+
<item>
56+
<widget class="QGroupBox" name="mTypeGroupBox">
57+
<layout class="QVBoxLayout" name="mTypeLayout"/>
58+
</widget>
59+
</item>
60+
<item>
61+
<widget class="QDialogButtonBox" name="mButtonBox">
62+
<property name="standardButtons">
63+
<set>QDialogButtonBox::Apply</set>
64+
</property>
65+
</widget>
66+
</item>
67+
</layout>
68+
</widget>
69+
<customwidgets>
70+
<customwidget>
71+
<class>QgsPanelWidget</class>
72+
<extends>QWidget</extends>
73+
<header>qgspanelwidget.h</header>
74+
<container>1</container>
75+
</customwidget>
76+
</customwidgets>
77+
<resources>
78+
<include location="../../../images/images.qrc"/>
79+
</resources>
80+
<connections/>
81+
</ui>

‎src/ui/sensor/qgssensortablewidgetbase.ui

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@
1010
<height>376</height>
1111
</rect>
1212
</property>
13-
<property name="windowTitle">
14-
<string>Sensors Table</string>
15-
</property>
1613
<layout class="QHBoxLayout" name="horizontalLayout">
1714
<property name="spacing">
1815
<number>3</number>
@@ -58,6 +55,48 @@
5855
</property>
5956
</widget>
6057
</item>
58+
<item>
59+
<widget class="QToolButton" name="mActionAddSensor">
60+
<property name="toolTip">
61+
<string>Create new sensor</string>
62+
</property>
63+
<property name="text">
64+
<string/>
65+
</property>
66+
<property name="icon">
67+
<iconset resource="../../images/images.qrc">
68+
<normaloff>:/images/themes/default/symbologyAdd.svg</normaloff>:/images/themes/default/symbologyAdd.svg</iconset>
69+
</property>
70+
</widget>
71+
</item>
72+
<item>
73+
<widget class="QToolButton" name="mActionRemoveSensor">
74+
<property name="toolTip">
75+
<string>Create new sensor</string>
76+
</property>
77+
<property name="text">
78+
<string/>
79+
</property>
80+
<property name="icon">
81+
<iconset resource="../../images/images.qrc">
82+
<normaloff>:/images/themes/default/symbologyRemove.svg</normaloff>:/images/themes/default/symbologyRemove.svg</iconset>
83+
</property>
84+
</widget>
85+
</item>
86+
<item>
87+
<widget class="QToolButton" name="mActionEditSensor">
88+
<property name="toolTip">
89+
<string>Edit sensor</string>
90+
</property>
91+
<property name="text">
92+
<string/>
93+
</property>
94+
<property name="icon">
95+
<iconset resource="../../images/images.qrc">
96+
<normaloff>:/images/themes/default/symbologyEdit.svg</normaloff>:/images/themes/default/symbologyEdit.svg</iconset>
97+
</property>
98+
</widget>
99+
</item>
61100
<item>
62101
<spacer name="verticalSpacer">
63102
<property name="orientation">
@@ -78,7 +117,7 @@
78117
<customwidgets>
79118
<customwidget>
80119
<class>QgsPanelWidget</class>
81-
<extends>QgsPanelWidget</extends>
120+
<extends>QWidget</extends>
82121
<header>qgspanelwidget.h</header>
83122
<container>1</container>
84123
</customwidget>
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ui version="4.0">
3+
<class>WidgetSerialPortSensor</class>
4+
<widget class="QWidget" name="WidgetSerialPortSensor">
5+
<property name="geometry">
6+
<rect>
7+
<x>0</x>
8+
<y>0</y>
9+
<width>371</width>
10+
<height>409</height>
11+
</rect>
12+
</property>
13+
<property name="windowTitle">
14+
<string notr="true">Form</string>
15+
</property>
16+
<layout class="QVBoxLayout" name="verticalLayout">
17+
<property name="leftMargin">
18+
<number>0</number>
19+
</property>
20+
<property name="topMargin">
21+
<number>0</number>
22+
</property>
23+
<property name="rightMargin">
24+
<number>0</number>
25+
</property>
26+
<property name="bottomMargin">
27+
<number>0</number>
28+
</property>
29+
<item>
30+
<widget class="QLabel" name="mSerialPortLabel">
31+
<property name="text">
32+
<string>Serial port name</string>
33+
</property>
34+
<property name="wordWrap">
35+
<bool>true</bool>
36+
</property>
37+
</widget>
38+
</item>
39+
<item>
40+
<widget class="QComboBox" name="mSerialPortComboBox"/>
41+
</item>
42+
<item>
43+
<widget class="QLabel" name="mSerialPortDetails">
44+
<property name="enabled">
45+
<bool>false</bool>
46+
</property>
47+
<property name="wordWrap">
48+
<bool>true</bool>
49+
</property>
50+
</widget>
51+
</item>
52+
<item>
53+
<spacer name="verticalSpacer">
54+
<property name="orientation">
55+
<enum>Qt::Vertical</enum>
56+
</property>
57+
<property name="sizeHint" stdset="0">
58+
<size>
59+
<width>20</width>
60+
<height>40</height>
61+
</size>
62+
</property>
63+
</spacer>
64+
</item>
65+
</layout>
66+
</widget>
67+
<resources/>
68+
<connections/>
69+
</ui>
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ui version="4.0">
3+
<class>WidgetTcpSocketSensor</class>
4+
<widget class="QWidget" name="WidgetTcpSocketSensor">
5+
<property name="geometry">
6+
<rect>
7+
<x>0</x>
8+
<y>0</y>
9+
<width>371</width>
10+
<height>409</height>
11+
</rect>
12+
</property>
13+
<property name="windowTitle">
14+
<string notr="true">Form</string>
15+
</property>
16+
<layout class="QVBoxLayout" name="verticalLayout">
17+
<property name="leftMargin">
18+
<number>0</number>
19+
</property>
20+
<property name="topMargin">
21+
<number>0</number>
22+
</property>
23+
<property name="rightMargin">
24+
<number>0</number>
25+
</property>
26+
<property name="bottomMargin">
27+
<number>0</number>
28+
</property>
29+
<item>
30+
<widget class="QLabel" name="mHostNameLabel">
31+
<property name="text">
32+
<string>Host name (a domain string or IP address)</string>
33+
</property>
34+
<property name="wordWrap">
35+
<bool>true</bool>
36+
</property>
37+
</widget>
38+
</item>
39+
<item>
40+
<widget class="QLineEdit" name="mHostNameLineEdit"/>
41+
</item>
42+
<item>
43+
<widget class="QLabel" name="mPortLabel">
44+
<property name="text">
45+
<string>Port</string>
46+
</property>
47+
<property name="wordWrap">
48+
<bool>true</bool>
49+
</property>
50+
</widget>
51+
</item>
52+
<item>
53+
<widget class="QgsSpinBox" name="mPortSpinBox">
54+
<property name="sizePolicy">
55+
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
56+
<horstretch>0</horstretch>
57+
<verstretch>0</verstretch>
58+
</sizepolicy>
59+
</property>
60+
<property name="prefix">
61+
<string/>
62+
</property>
63+
<property name="minimum">
64+
<number>1</number>
65+
</property>
66+
<property name="maximum">
67+
<number>65535</number>
68+
</property>
69+
<property name="showClearButton" stdset="0">
70+
<bool>false</bool>
71+
</property>
72+
</widget>
73+
</item>
74+
<item>
75+
<spacer name="verticalSpacer">
76+
<property name="orientation">
77+
<enum>Qt::Vertical</enum>
78+
</property>
79+
<property name="sizeHint" stdset="0">
80+
<size>
81+
<width>20</width>
82+
<height>40</height>
83+
</size>
84+
</property>
85+
</spacer>
86+
</item>
87+
</layout>
88+
</widget>
89+
<customwidgets>
90+
<customwidget>
91+
<class>QgsSpinBox</class>
92+
<extends>QSpinBox</extends>
93+
<header>qgsspinbox.h</header>
94+
</customwidget>
95+
</customwidgets>
96+
<resources/>
97+
<connections/>
98+
</ui>
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ui version="4.0">
3+
<class>WidgetUdpSocketSensor</class>
4+
<widget class="QWidget" name="WidgetUdpSocketSensor">
5+
<property name="geometry">
6+
<rect>
7+
<x>0</x>
8+
<y>0</y>
9+
<width>371</width>
10+
<height>409</height>
11+
</rect>
12+
</property>
13+
<property name="windowTitle">
14+
<string notr="true">Form</string>
15+
</property>
16+
<layout class="QVBoxLayout" name="verticalLayout">
17+
<property name="leftMargin">
18+
<number>0</number>
19+
</property>
20+
<property name="topMargin">
21+
<number>0</number>
22+
</property>
23+
<property name="rightMargin">
24+
<number>0</number>
25+
</property>
26+
<property name="bottomMargin">
27+
<number>0</number>
28+
</property>
29+
<item>
30+
<widget class="QLabel" name="mHostNameLabel">
31+
<property name="text">
32+
<string>Host name (a domain string or IP address)</string>
33+
</property>
34+
<property name="wordWrap">
35+
<bool>true</bool>
36+
</property>
37+
</widget>
38+
</item>
39+
<item>
40+
<widget class="QLineEdit" name="mHostNameLineEdit"/>
41+
</item>
42+
<item>
43+
<widget class="QLabel" name="mPortLabel">
44+
<property name="text">
45+
<string>Port</string>
46+
</property>
47+
<property name="wordWrap">
48+
<bool>true</bool>
49+
</property>
50+
</widget>
51+
</item>
52+
<item>
53+
<widget class="QgsSpinBox" name="mPortSpinBox">
54+
<property name="sizePolicy">
55+
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
56+
<horstretch>0</horstretch>
57+
<verstretch>0</verstretch>
58+
</sizepolicy>
59+
</property>
60+
<property name="prefix">
61+
<string/>
62+
</property>
63+
<property name="minimum">
64+
<number>1</number>
65+
</property>
66+
<property name="maximum">
67+
<number>65535</number>
68+
</property>
69+
<property name="showClearButton" stdset="0">
70+
<bool>false</bool>
71+
</property>
72+
</widget>
73+
</item>
74+
<item>
75+
<spacer name="verticalSpacer">
76+
<property name="orientation">
77+
<enum>Qt::Vertical</enum>
78+
</property>
79+
<property name="sizeHint" stdset="0">
80+
<size>
81+
<width>20</width>
82+
<height>40</height>
83+
</size>
84+
</property>
85+
</spacer>
86+
</item>
87+
</layout>
88+
</widget>
89+
<customwidgets>
90+
<customwidget>
91+
<class>QgsSpinBox</class>
92+
<extends>QSpinBox</extends>
93+
<header>qgsspinbox.h</header>
94+
</customwidget>
95+
</customwidgets>
96+
<resources/>
97+
<connections/>
98+
</ui>

0 commit comments

Comments
 (0)
Please sign in to comment.