[ui][sensors] Add a sensors panel in the project properties dialog

This commit is contained in:
Mathieu Pellerin 2023-03-21 14:03:37 +07:00
parent 99f8005d6b
commit 00b53e7c30
16 changed files with 481 additions and 1 deletions

View File

@ -639,6 +639,7 @@
<file>themes/default/propertyicons/colors.svg</file>
<file>themes/default/propertyicons/system.svg</file>
<file>themes/default/propertyicons/transparency.svg</file>
<file>themes/default/propertyicons/sensor.svg</file>
<file>themes/default/propertyicons/spacer.svg</file>
<file>themes/default/propertyicons/relations.svg</file>
<file>themes/default/rendererCategorizedSymbol.svg</file>

View File

@ -1 +1,5 @@
<svg height="48" width="48" xmlns="http://www.w3.org/2000/svg"><path d="m12 12h24v24h-24z" fill="#747474"/></svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg width="48" height="48" version="1.1" xmlns="http://www.w3.org/2000/svg">
<path d="m12 12h24v24h-24z" fill="#505050" stroke="#fff" stroke-opacity=".50196" stroke-width="2"/>
<path d="m12 12h24v24h-24z" fill="#505050"/>
</svg>

Before

Width:  |  Height:  |  Size: 113 B

After

Width:  |  Height:  |  Size: 271 B

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1.1" xmlns="http://www.w3.org/2000/svg">
<ellipse cx="12" cy="12" rx="3" ry="3" fill="#6d97c4" stroke="#415a75" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" style="paint-order:stroke fill markers"/>
<g fill-opacity="0" stroke="#415a75">
<path d="m5.0405 4.6808c-3.4962 3.3909-4.5736 10.076 0 14.646" stroke-width="2"/>
<path d="m7.5991 6.9303c-2.809 2.8774-2.8198 7.1136-0.029272 10.157" stroke-width="1px"/>
<path d="m18.962 19.319c3.4962-3.3909 4.5736-10.076 0-14.646" stroke-width="2"/>
<path d="m16.383 17.069c2.809-2.8774 2.8198-7.1136 0.02928-10.157" stroke-width="1px"/>
</g>
<path d="m12 15c-1.0233-0.32686-1.5124-1.6684-1.5-3 0.01259-1.355 0.54381-2.6999 1.5-3-1.9333 0.030933-3.0114 1.5549-3.0001 3 0.010995 1.4086 0.98753 2.9233 3.0001 3z" fill="#bacee3"/>
</svg>

After

Width:  |  Height:  |  Size: 882 B

View File

@ -56,6 +56,12 @@ The default implementation returns ``True``.
%Docstring
Called to permanently apply the settings shown in the options page (e.g. save them to
:py:class:`QgsSettings` objects). This is usually called when the options dialog is accepted.
%End
virtual void cancel();
%Docstring
Called to cancel settings changed in the options page (e.g. save them to
:py:class:`QgsSettings` objects). This is usually called when the options dialog is canceled.
%End
protected:

View File

@ -272,6 +272,9 @@ set(QGIS_APP_SRCS
gps/qgsgpsmarker.cpp
gps/qgsgpstoolbar.cpp
sensor/qgssensortablewidget.cpp
sensor/qgsprojectsensorsettingswidget.cpp
project/qgsprojectelevationsettingswidget.cpp
pluginmanager/qgspluginmanager.cpp
@ -525,6 +528,7 @@ target_include_directories(qgis_app PUBLIC
${CMAKE_SOURCE_DIR}/src/app/layout
${CMAKE_SOURCE_DIR}/src/app/pluginmanager
${CMAKE_SOURCE_DIR}/src/app/gps
${CMAKE_SOURCE_DIR}/src/app/sensor
${CMAKE_SOURCE_DIR}/src/app/dwg
${CMAKE_SOURCE_DIR}/src/app/maptools
${CMAKE_SOURCE_DIR}/src/app/mesh

View File

@ -442,7 +442,9 @@ Q_GUI_EXPORT extern int qt_defaultDpiX();
#include "pointcloud/qgspointcloudlayerstylewidget.h"
#include "pointcloud/qgspointcloudlayersaveasdialog.h"
#include "pointcloud/qgspointcloudlayerexporter.h"
#include "project/qgsprojectelevationsettingswidget.h"
#include "sensor/qgsprojectsensorsettingswidget.h"
#include "qgsmaptoolsdigitizingtechniquemanager.h"
#include "qgsmaptoolshaperegistry.h"
@ -1507,6 +1509,7 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipBadLayers
registerMapLayerPropertiesFactory( new QgsElevationShadingRendererSettingsWidgetFactory( this ) );
registerProjectPropertiesWidgetFactory( new QgsProjectElevationSettingsWidgetFactory( this ) );
registerProjectPropertiesWidgetFactory( new QgsProjectSensorSettingsWidgetFactory( this ) );
activateDeactivateLayerRelatedActions( nullptr ); // after members were created

View File

@ -182,6 +182,7 @@ QgsProjectProperties::QgsProjectProperties( QgsMapCanvas *mapCanvas, QWidget *pa
projectionSelector->setShowNoProjection( true );
connect( buttonBox->button( QDialogButtonBox::Apply ), &QAbstractButton::clicked, this, &QgsProjectProperties::apply );
connect( this, &QDialog::finished, this, [ = ]( int result ) { if ( result == QDialog::Rejected ) cancel(); } );
// disconnect default connection setup by initOptionsBase for accepting dialog, and insert logic
// to validate widgets before allowing dialog to be closed
@ -1738,6 +1739,14 @@ void QgsProjectProperties::apply()
}
}
void QgsProjectProperties::cancel()
{
for ( QgsOptionsPageWidget *widget : std::as_const( mAdditionalProjectPropertiesWidgets ) )
{
widget->cancel();
}
}
void QgsProjectProperties::lwWmsRowsInserted( const QModelIndex &parent, int first, int last )
{
Q_UNUSED( parent )

View File

@ -80,6 +80,11 @@ class APP_EXPORT QgsProjectProperties : public QgsOptionsDialogBase, private Ui:
*/
void apply();
/**
* Slot called when cancel button is pressed or dialog is not accepted
*/
void cancel();
/**
* Let the user add a scale to the list of project scales
* used in scale combobox instead of global ones.

View File

@ -0,0 +1,93 @@
/***************************************************************************
qgsprojectsensorsettingswidget.cpp
---------------------
begin : March 2022
copyright : (C) 2022 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsprojectsensorsettingswidget.h"
#include "qgis.h"
#include "qgsabstractsensor.h"
#include "qgsapplication.h"
#include "qgsproject.h"
#include "qgssensormanager.h"
QgsProjectSensorSettingsWidget::QgsProjectSensorSettingsWidget( QWidget *parent )
: QgsOptionsPageWidget( parent )
{
setupUi( this );
QDomElement sensorElem = QgsProject::instance()->sensorManager()->writeXml( mPreviousSensors );
mPreviousSensors.appendChild( sensorElem );
const QList<QgsAbstractSensor *> sensors = QgsProject::instance()->sensorManager()->sensors();
for ( QgsAbstractSensor *sensor : sensors )
{
if ( sensor->status() == Qgis::DeviceConnectionStatus::Connected )
{
mConnectedSensors << sensor->id();
}
}
}
void QgsProjectSensorSettingsWidget::cancel()
{
// Capture connected state of current sensors even if we're about to revert as someone might have
// activated a sensor then closed the dialog using the window bar's close button
QList<QgsAbstractSensor *> sensors = QgsProject::instance()->sensorManager()->sensors();
for ( QgsAbstractSensor *sensor : sensors )
{
if ( sensor->status() == Qgis::DeviceConnectionStatus::Connected )
{
mConnectedSensors << sensor->id();
}
}
QgsProject::instance()->sensorManager()->clear();
QgsProject::instance()->sensorManager()->readXml( mPreviousSensors.documentElement(), mPreviousSensors );
sensors = QgsProject::instance()->sensorManager()->sensors();
for ( QgsAbstractSensor *sensor : sensors )
{
if ( mConnectedSensors.contains( sensor->id() ) )
{
sensor->connectSensor();
}
}
}
void QgsProjectSensorSettingsWidget::apply()
{
return;
}
bool QgsProjectSensorSettingsWidget::isValid()
{
return true;
}
//
// QgsProjectSensorSettingsWidgetFactory
//
QgsProjectSensorSettingsWidgetFactory::QgsProjectSensorSettingsWidgetFactory( QObject *parent )
: QgsOptionsWidgetFactory( tr( "Sensors" ), QgsApplication::getThemeIcon( QStringLiteral( "propertyicons/sensor.svg" ) ), QStringLiteral( "sensor" ) )
{
setParent( parent );
}
QgsOptionsPageWidget *QgsProjectSensorSettingsWidgetFactory::createWidget( QWidget *parent ) const
{
return new QgsProjectSensorSettingsWidget( parent );
}

View File

@ -0,0 +1,55 @@
/***************************************************************************
qgsprojectsensorsettingswidget.h
---------------------
begin : March 2023
copyright : (C) 2023 by Mathieu Pellerin
email : mathieu at opengis dot ch
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSPROJECTSENSORSETTINGSWIDGET_H
#define QGSPROJECTSENSORSETTINGSWIDGET_H
#include "ui_qgsprojectsensorettingswidgetbase.h"
#include "qgsoptionswidgetfactory.h"
#include <QDomDocument>
class QgsProjectSensorSettingsWidget : public QgsOptionsPageWidget, private Ui::QgsProjectSensorSettingsWidgetBase
{
Q_OBJECT
public:
QgsProjectSensorSettingsWidget( QWidget *parent = nullptr );
public slots:
bool isValid() override;
void apply() override;
void cancel() override;
private:
QDomDocument mPreviousSensors;
QStringList mConnectedSensors;
};
class QgsProjectSensorSettingsWidgetFactory : public QgsOptionsWidgetFactory
{
Q_OBJECT
public:
explicit QgsProjectSensorSettingsWidgetFactory( QObject *parent = nullptr );
QgsOptionsPageWidget *createWidget( QWidget *parent = nullptr ) const override;
};
#endif // QGSPROJECTSENSORSETTINGSWIDGET_H

View File

@ -0,0 +1,97 @@
/***************************************************************************
qgssensortablewidget.h
---------------------------------
begin : March 2023
copyright : (C) 2023 by Mathieu Pellerin
email : mathieu at opengis dot ch
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgssensortablewidget.h"
#include "qgisapp.h"
#include "qgsapplication.h"
#include "qgssensormanager.h"
#include "qgssensormodel.h"
#include "qgsproject.h"
#include <QTableWidget>
QgsSensorTableWidget::QgsSensorTableWidget( QWidget *parent )
: QgsPanelWidget( parent )
{
setupUi( this );
setPanelTitle( tr( "Sensors List" ) );
setObjectName( QStringLiteral( "SensorsList" ) );
mActionConnection->setEnabled( false );
mSensorModel = new QgsSensorModel( QgsProject::instance()->sensorManager(), this );
mSensorTable->setModel( mSensorModel );
mSensorTable->horizontalHeader()->setSectionResizeMode( static_cast<int>( QgsSensorModel::Column::Name ), QHeaderView::Stretch );
mSensorTable->setSelectionBehavior( QAbstractItemView::SelectRows );
mSensorTable->setSelectionMode( QAbstractItemView::SingleSelection );
connect( QgsProject::instance()->sensorManager(), &QgsSensorManager::sensorStatusChanged, this, [ = ]( const QString & id )
{
const QModelIndex index = mSensorTable->currentIndex();
if ( index.isValid() )
{
if ( id == mSensorModel->data( index, QgsSensorModel::SensorId ).toString() )
{
QgsAbstractSensor *sensor = mSensorModel->data( index, QgsSensorModel::Sensor ).value<QgsAbstractSensor *>();
if ( sensor )
{
if ( sensor->status() == Qgis::DeviceConnectionStatus::Disconnected )
{
mActionConnection->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionStart.svg" ) ) );
}
else
{
mActionConnection->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionStop.svg" ) ) );
}
}
}
}
} );
connect( mSensorTable->selectionModel(), &QItemSelectionModel::currentChanged, this, [ = ]( const QModelIndex & current, const QModelIndex & )
{
mActionConnection->setEnabled( current.isValid() );
if ( current.isValid() && mSensorModel->data( current, QgsSensorModel::SensorStatus ).value<Qgis::DeviceConnectionStatus>() == Qgis::DeviceConnectionStatus::Connected )
{
mActionConnection->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionStop.svg" ) ) );
}
else
{
mActionConnection->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionStart.svg" ) ) );
}
} );
connect( mActionConnection, &QToolButton::clicked, this, [ = ]()
{
const QModelIndex index = mSensorTable->currentIndex();
if ( index.isValid() )
{
QgsAbstractSensor *sensor = mSensorModel->data( index, QgsSensorModel::Sensor ).value<QgsAbstractSensor *>();
if ( sensor )
{
if ( sensor->status() == Qgis::DeviceConnectionStatus::Disconnected )
{
sensor->connectSensor();
}
else
{
sensor->disconnectSensor();
}
}
}
} );
}

View File

@ -0,0 +1,40 @@
/***************************************************************************
qgssensortablewidget.h
---------------------------------
begin : March 2023
copyright : (C) 2023 by Mathieu Pellerin
email : mathieu at opengis dot ch
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSSENSORTABLEWIDGET_H
#define QGSSENSORTABLEWIDGET_H
#include "ui_qgssensortablewidgetbase.h"
#include "qgis_app.h"
#include "qgsdockwidget.h"
#include "qgspanelwidget.h"
#include "qgspanelwidgetstack.h"
class QgsSensorModel;
class APP_EXPORT QgsSensorTableWidget : public QgsPanelWidget, private Ui::QgsSensorTableWidgetBase
{
Q_OBJECT
public:
QgsSensorTableWidget( QWidget *parent = nullptr );
~QgsSensorTableWidget() override = default;
private:
QgsSensorModel *mSensorModel = nullptr;
};
#endif // QGSSENSORTABLEWIDGET_H

View File

@ -77,6 +77,12 @@ class GUI_EXPORT QgsOptionsPageWidget : public QWidget
*/
virtual void apply() = 0;
/**
* Called to cancel settings changed in the options page (e.g. save them to
* QgsSettings objects). This is usually called when the options dialog is canceled.
*/
virtual void cancel() { return; }
protected:
/**

View File

@ -17,6 +17,7 @@ file(GLOB NUMERICFORMAT_UIS "${CMAKE_CURRENT_SOURCE_DIR}/numericformats/*.ui")
file(GLOB ATTRIBUTEFORMCONFIG_UIS "${CMAKE_CURRENT_SOURCE_DIR}/attributeformconfig/*.ui")
file(GLOB GEOREFERENCER_UIS "${CMAKE_CURRENT_SOURCE_DIR}/georeferencer/*.ui")
file(GLOB ANNOTATION_UIS "${CMAKE_CURRENT_SOURCE_DIR}/annotations/*.ui")
file(GLOB SENSOR_UIS "${CMAKE_CURRENT_SOURCE_DIR}/sensor/*.ui")
if (BUILD_WITH_QT6)
QT6_WRAP_UI(QGIS_UIS_H
@ -38,6 +39,7 @@ if (BUILD_WITH_QT6)
${ATTRIBUTEFORMCONFIG_UIS}
${GEOREFERENCER_UIS}
${ANNOTATION_UIS}
${SENSOR_UIS}
)
else()
QT5_WRAP_UI(QGIS_UIS_H
@ -59,6 +61,7 @@ else()
${ATTRIBUTEFORMCONFIG_UIS}
${GEOREFERENCER_UIS}
${ANNOTATION_UIS}
${SENSOR_UIS}
)
endif()

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QgsProjectSensorSettingsWidgetBase</class>
<widget class="QWidget" name="QgsProjectSensorSettingsWidgetBase">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>504</width>
<height>486</height>
</rect>
</property>
<property name="windowTitle">
<string>Sensor Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Sensor Settings</string>
</property>
<layout class="QVBoxLayout" name="mainLayout">
<item>
<widget class="QgsSensorTableWidget" name="mSensorTableWidget"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QgsSensorTableWidget</class>
<extends>QWidget</extends>
<header>qgssensortablewidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QgsSensorTableWidgetBase</class>
<widget class="QgsPanelWidget" name="QgsSensorTableWidgetBase">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>291</width>
<height>376</height>
</rect>
</property>
<property name="windowTitle">
<string>Sensors Table</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTableView" name="mSensorTable">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<attribute name="horizontalHeaderVisible">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="actionsLayout">
<item>
<widget class="QToolButton" name="mActionConnection">
<property name="toolTip">
<string>Toggle sensor connection status</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../images/images.qrc">
<normaloff>:/images/themes/default/mActionStart.svg</normaloff>:/images/themes/default/mActionStart.svg</iconset>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QgsPanelWidget</class>
<extends>QgsPanelWidget</extends>
<header>qgspanelwidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="../../../images/images.qrc"/>
</resources>
<connections/>
</ui>