diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt
index e474aa9b2..00c0041cc 100644
--- a/app/CMakeLists.txt
+++ b/app/CMakeLists.txt
@@ -32,8 +32,10 @@ set(MM_SRCS
ios/iosutils.cpp
position/providers/abstractpositionprovider.cpp
position/providers/internalpositionprovider.cpp
+ position/providers/networkpositionprovider.cpp
position/providers/positionprovidersmodel.cpp
position/providers/simulatedpositionprovider.cpp
+ position/providers/nmeaparser.cpp
position/tracking/abstracttrackingbackend.cpp
position/tracking/internaltrackingbackend.cpp
position/tracking/positiontrackinghighlight.cpp
@@ -122,8 +124,10 @@ set(MM_HDRS
ios/iosutils.h
position/providers/abstractpositionprovider.h
position/providers/internalpositionprovider.h
+ position/providers/networkpositionprovider.h
position/providers/positionprovidersmodel.h
position/providers/simulatedpositionprovider.h
+ position/providers/nmeaparser.h
position/tracking/abstracttrackingbackend.h
position/tracking/internaltrackingbackend.h
position/tracking/positiontrackinghighlight.h
diff --git a/app/appsettings.cpp b/app/appsettings.cpp
index 58e340ec1..e781e108a 100644
--- a/app/appsettings.cpp
+++ b/app/appsettings.cpp
@@ -215,6 +215,7 @@ QVariantList AppSettings::savedPositionProviders() const
QStringList provider;
provider << settings.value( "providerName" ).toString();
provider << settings.value( "providerAddress" ).toString();
+ provider << settings.value( "providerType" ).toString();
providers.push_back( provider );
}
@@ -238,15 +239,16 @@ void AppSettings::savePositionProviders( const QVariantList &providers )
{
QVariantList provider = providers[i].toList();
- if ( provider.length() < 2 )
+ if ( provider.length() < 3 )
{
CoreUtils::log( QStringLiteral( "AppSettings" ), QStringLiteral( "Tried to save provider without sufficient data" ) );
continue;
}
settings.setArrayIndex( i );
- settings.setValue( "providerName", providers[i].toList()[0] );
- settings.setValue( "providerAddress", providers[i].toList()[1] );
+ settings.setValue( "providerName", provider[0] );
+ settings.setValue( "providerAddress", provider[1] );
+ settings.setValue( "providerType", provider[2] );
}
settings.endArray();
}
diff --git a/app/icons/Bluetooth.svg b/app/icons/Bluetooth.svg
new file mode 100644
index 000000000..f18e6d485
--- /dev/null
+++ b/app/icons/Bluetooth.svg
@@ -0,0 +1,3 @@
+
diff --git a/app/icons/Network.svg b/app/icons/Network.svg
new file mode 100644
index 000000000..3ff4e7b3d
--- /dev/null
+++ b/app/icons/Network.svg
@@ -0,0 +1,6 @@
+
diff --git a/app/icons/icons.qrc b/app/icons/icons.qrc
index 8d9a78db9..261db4fb3 100644
--- a/app/icons/icons.qrc
+++ b/app/icons/icons.qrc
@@ -7,6 +7,7 @@
ArrowLinkRight.svg
ArrowUp.svg
Back.svg
+ Bluetooth.svg
Briefcase.svg
Calendar.svg
Checkmark.svg
@@ -57,6 +58,7 @@
Mouth.svg
NaturalResources.svg
Next.svg
+ Network.svg
Other.svg
Others.svg
Personal.svg
diff --git a/app/images/ExternalBluetoothProvider.svg b/app/images/ExternalBluetoothProvider.svg
new file mode 100644
index 000000000..35f92c7bd
--- /dev/null
+++ b/app/images/ExternalBluetoothProvider.svg
@@ -0,0 +1,274 @@
+
diff --git a/app/images/ExternalGpsGreen.svg b/app/images/ExternalGpsGreen.svg
deleted file mode 100644
index d5a833bd7..000000000
--- a/app/images/ExternalGpsGreen.svg
+++ /dev/null
@@ -1,19 +0,0 @@
-
diff --git a/app/images/ExternalGpsRed.svg b/app/images/ExternalGpsRed.svg
index 9fd5a52d2..67f981956 100644
--- a/app/images/ExternalGpsRed.svg
+++ b/app/images/ExternalGpsRed.svg
@@ -1,14 +1,276 @@
diff --git a/app/images/ExternalNetworkProvider.svg b/app/images/ExternalNetworkProvider.svg
new file mode 100644
index 000000000..a2602f99f
--- /dev/null
+++ b/app/images/ExternalNetworkProvider.svg
@@ -0,0 +1,277 @@
+
diff --git a/app/images/images.qrc b/app/images/images.qrc
index b0b772a30..2d2cf69f2 100644
--- a/app/images/images.qrc
+++ b/app/images/images.qrc
@@ -19,8 +19,9 @@
UploadImage.svg
WarnLogoImage.svg
NoMapThemesImage.svg
+ ExternalBluetoothProvider.svg
ExternalGpsRed.svg
- ExternalGpsGreen.svg
+ ExternalNetworkProvider.svg
NegativeMMSymbol.svg
PositiveMMSymbol.svg
NeutralMMSymbol.svg
diff --git a/app/mmstyle.h b/app/mmstyle.h
index 1dc74b4f6..f51f4a567 100644
--- a/app/mmstyle.h
+++ b/app/mmstyle.h
@@ -112,6 +112,7 @@ class MMStyle: public QObject
Q_PROPERTY( QUrl arrowDownIcon READ arrowDownIcon CONSTANT )
Q_PROPERTY( QUrl arrowLinkRightIcon READ arrowLinkRightIcon CONSTANT )
Q_PROPERTY( QUrl arrowUpIcon READ arrowUpIcon CONSTANT )
+ Q_PROPERTY( QUrl bluetoothIcon READ bluetoothIcon CONSTANT )
Q_PROPERTY( QUrl backIcon READ backIcon CONSTANT )
Q_PROPERTY( QUrl briefcaseIcon READ briefcaseIcon CONSTANT )
Q_PROPERTY( QUrl calendarIcon READ calendarIcon CONSTANT )
@@ -144,6 +145,7 @@ class MMStyle: public QObject
Q_PROPERTY( QUrl mouthIcon READ mouthIcon CONSTANT )
Q_PROPERTY( QUrl naturalResourcesIcon READ naturalResourcesIcon CONSTANT )
Q_PROPERTY( QUrl nextIcon READ nextIcon CONSTANT )
+ Q_PROPERTY( QUrl networkIcon READ networkIcon CONSTANT )
Q_PROPERTY( QUrl otherIcon READ otherIcon CONSTANT )
Q_PROPERTY( QUrl othersIcon READ othersIcon CONSTANT )
Q_PROPERTY( QUrl plusIcon READ plusIcon CONSTANT )
@@ -227,8 +229,9 @@ class MMStyle: public QObject
Q_PROPERTY( QUrl positionTrackingRunningImage READ positionTrackingRunningImage CONSTANT )
Q_PROPERTY( QUrl positionTrackingStartImage READ positionTrackingStartImage CONSTANT )
Q_PROPERTY( QUrl syncImage READ syncImage CONSTANT )
- Q_PROPERTY( QUrl externalGpsGreenImage READ externalGpsGreenImage CONSTANT )
Q_PROPERTY( QUrl externalGpsRedImage READ externalGpsRedImage CONSTANT )
+ Q_PROPERTY( QUrl externalBluetoothGreenImage READ externalBluetoothGreenImage CONSTANT )
+ Q_PROPERTY( QUrl externalNetworkGreenImage READ externalNetworkGreenImage CONSTANT )
Q_PROPERTY( QUrl negativeMMSymbolImage READ negativeMMSymbolImage CONSTANT )
Q_PROPERTY( QUrl positiveMMSymbolImage READ positiveMMSymbolImage CONSTANT )
Q_PROPERTY( QUrl neutralMMSymbolImage READ neutralMMSymbolImage CONSTANT )
@@ -424,6 +427,7 @@ class MMStyle: public QObject
QUrl arrowLinkRightIcon() const {return QUrl( "qrc:/ArrowLinkRight.svg" );}
QUrl arrowUpIcon() const {return QUrl( "qrc:/ArrowUp.svg" );}
QUrl backIcon() const {return QUrl( "qrc:/Back.svg" );}
+ QUrl bluetoothIcon() const {return QUrl( "qrc:/Bluetooth.svg" );}
QUrl briefcaseIcon() const {return QUrl( "qrc:/Briefcase.svg" );}
QUrl calendarIcon() const {return QUrl( "qrc:/Calendar.svg" );}
QUrl checkmarkIcon() const {return QUrl( "qrc:/Checkmark.svg" );}
@@ -454,6 +458,7 @@ class MMStyle: public QObject
QUrl remoteImageLoadErrorIcon() const {return QUrl( "qrc:/RemoteImageLoadError.svg" );}
QUrl mouthIcon() const {return QUrl( "qrc:/Mouth.svg" );}
QUrl measurementToolIcon() const {return QUrl( "qrc:/Measure.svg" );}
+ QUrl networkIcon() const {return QUrl( "qrc:/Network.svg" );}
QUrl closeShapeIcon() const {return QUrl( "qrc:/CloseShape.svg" );}
QUrl naturalResourcesIcon() const {return QUrl( "qrc:/NaturalResources.svg" );}
QUrl nextIcon() const {return QUrl( "qrc:/Next.svg" );}
@@ -530,8 +535,9 @@ class MMStyle: public QObject
QUrl positionTrackingRunningImage() const {return QUrl( "qrc:/images/PositionTrackingRunning.svg" );}
QUrl positionTrackingStartImage() const {return QUrl( "qrc:/images/PositionTrackingStart.svg" );}
QUrl syncImage() const {return QUrl( "qrc:/images/SyncImage.svg" );}
- QUrl externalGpsGreenImage() const {return QUrl( "qrc:/images/ExternalGpsGreen.svg" );}
QUrl externalGpsRedImage() const {return QUrl( "qrc:/images/ExternalGpsRed.svg" );}
+ QUrl externalBluetoothGreenImage() const {return QUrl( "qrc:/images/ExternalBluetoothProvider.svg" );}
+ QUrl externalNetworkGreenImage() const {return QUrl( "qrc:/images/ExternalNetworkProvider.svg" );}
QUrl negativeMMSymbolImage() const {return QUrl( "qrc:/images/NegativeMMSymbol.svg" );}
QUrl positiveMMSymbolImage() const {return QUrl( "qrc:/images/PositiveMMSymbol.svg" );}
QUrl neutralMMSymbolImage() const {return QUrl( "qrc:/images/NeutralMMSymbol.svg" );}
diff --git a/app/position/positionkit.cpp b/app/position/positionkit.cpp
index 85c6677a5..509144c80 100644
--- a/app/position/positionkit.cpp
+++ b/app/position/positionkit.cpp
@@ -18,6 +18,7 @@
#include "position/providers/internalpositionprovider.h"
#include "position/providers/simulatedpositionprovider.h"
+#include "providers/networkpositionprovider.h"
#ifdef ANDROID
#include "position/providers/androidpositionprovider.h"
#include
@@ -127,54 +128,57 @@ QString PositionKit::positionProviderName() const
AbstractPositionProvider *PositionKit::constructProvider( const QString &type, const QString &id, const QString &name )
{
QString providerType( type );
-
- // currently the only external provider is bluetooth, so manually set internal provider for platforms that
- // do not support reading bluetooth serial
-#ifndef HAVE_BLUETOOTH
- providerType = QStringLiteral( "internal" );
-#endif
-
- if ( providerType == QStringLiteral( "external" ) )
+ if ( providerType == QStringLiteral( "external_bt" ) )
{
#ifdef HAVE_BLUETOOTH
AbstractPositionProvider *provider = new BluetoothPositionProvider( id, name, *mPositionTransformer );
QQmlEngine::setObjectOwnership( provider, QQmlEngine::CppOwnership );
return provider;
+#else
+ providerType = QStringLiteral( "internal" );
#endif
}
- else // type == internal
+
+ if ( providerType == QStringLiteral( "external_ip" ) )
{
- if ( id == QStringLiteral( "simulated" ) )
- {
- AbstractPositionProvider *provider = new SimulatedPositionProvider( *mPositionTransformer );
- QQmlEngine::setObjectOwnership( provider, QQmlEngine::CppOwnership );
- return provider;
- }
+ AbstractPositionProvider *provider = new NetworkPositionProvider( id, name, *mPositionTransformer );
+ QQmlEngine::setObjectOwnership( provider, QQmlEngine::CppOwnership );
+ return provider;
+ }
+
+ // type == internal
+ if ( id == QStringLiteral( "simulated" ) )
+ {
+ AbstractPositionProvider *provider = new SimulatedPositionProvider( *mPositionTransformer );
+ QQmlEngine::setObjectOwnership( provider, QQmlEngine::CppOwnership );
+ return provider;
+ }
+
#ifdef ANDROID
- if ( id == QStringLiteral( "android_fused" ) || id == QStringLiteral( "android_gps" ) )
+ if ( id == QStringLiteral( "android_fused" ) || id == QStringLiteral( "android_gps" ) )
+ {
+ const bool fused = ( id == QStringLiteral( "android_fused" ) );
+ if ( fused && !AndroidPositionProvider::isFusedAvailable() )
{
- const bool fused = ( id == QStringLiteral( "android_fused" ) );
- if ( fused && !AndroidPositionProvider::isFusedAvailable() )
- {
- // TODO: inform user + use AndroidPositionProvider::fusedErrorString() output?
+ // TODO: inform user + use AndroidPositionProvider::fusedErrorString() output?
- // fallback to the default - at this point the Qt Positioning implementation
- AbstractPositionProvider *provider = new InternalPositionProvider( *mPositionTransformer );
- QQmlEngine::setObjectOwnership( provider, QQmlEngine::CppOwnership );
- return provider;
- }
- __android_log_print( ANDROID_LOG_INFO, "CPP", "MAKE PROVIDER %d", fused );
- AbstractPositionProvider *provider = new AndroidPositionProvider( fused, *mPositionTransformer );
- QQmlEngine::setObjectOwnership( provider, QQmlEngine::CppOwnership );
- return provider;
- }
-#endif
- else // id == devicegps
- {
+ // fallback to the default - at this point the Qt Positioning implementation
AbstractPositionProvider *provider = new InternalPositionProvider( *mPositionTransformer );
QQmlEngine::setObjectOwnership( provider, QQmlEngine::CppOwnership );
return provider;
}
+ __android_log_print( ANDROID_LOG_INFO, "CPP", "MAKE PROVIDER %d", fused );
+ AbstractPositionProvider *provider = new AndroidPositionProvider( fused, *mPositionTransformer );
+ QQmlEngine::setObjectOwnership( provider, QQmlEngine::CppOwnership );
+ return provider;
+ }
+#endif
+
+ // id == devicegps
+ {
+ AbstractPositionProvider *provider = new InternalPositionProvider( *mPositionTransformer );
+ QQmlEngine::setObjectOwnership( provider, QQmlEngine::CppOwnership );
+ return provider;
}
}
@@ -207,8 +211,10 @@ AbstractPositionProvider *PositionKit::constructActiveProvider( AppSettings *app
}
else
{
- // find name of the active provider
+ // find name & type of the active provider
QString providerName;
+ // Migration from single external provider to multiple currently, missing type == bluetooth provider
+ QString providerType = QStringLiteral( "external_bt" );
QVariantList providers = appsettings->savedPositionProviders();
for ( const auto &provider : providers )
@@ -223,10 +229,14 @@ AbstractPositionProvider *PositionKit::constructActiveProvider( AppSettings *app
if ( providerData[1] == providerId )
{
providerName = providerData[0].toString();
+ if ( !providerData.at( 2 ).isNull() )
+ {
+ providerType = providerData[2].toString();
+ }
}
}
- return constructProvider( QStringLiteral( "external" ), providerId, providerName );
+ return constructProvider( providerType, providerId, providerName );
}
}
diff --git a/app/position/positiontransformer.cpp b/app/position/positiontransformer.cpp
index 3d5dd559c..52071dd16 100644
--- a/app/position/positiontransformer.cpp
+++ b/app/position/positiontransformer.cpp
@@ -50,6 +50,11 @@ GeoPosition PositionTransformer::processBluetoothPosition( GeoPosition geoPositi
return geoPosition;
}
+GeoPosition PositionTransformer::processNetworkPosition( const GeoPosition &geoPosition )
+{
+ return processBluetoothPosition( geoPosition );
+}
+
GeoPosition PositionTransformer::processAndroidPosition( GeoPosition geoPosition )
{
if ( geoPosition.elevation != std::numeric_limits::quiet_NaN() )
diff --git a/app/position/positiontransformer.h b/app/position/positiontransformer.h
index 87e4a2c6a..0bac4de0f 100644
--- a/app/position/positiontransformer.h
+++ b/app/position/positiontransformer.h
@@ -49,6 +49,14 @@ class PositionTransformer : QObject
*/
GeoPosition processBluetoothPosition( GeoPosition geoPosition );
+ /**
+ * Transform the elevation if the user sets custom vertical CRS. The elevation gets recalculated to ellipsoid elevation
+ * and then back to orthometric based on specified CRS.
+ * \note This method should be used only with NetworkPositionProvider to mitigate unnecessary transformations
+ * \return Copy of passed geoPosition with processed elevation and elevation separation.
+ */
+ GeoPosition processNetworkPosition( const GeoPosition &geoPosition );
+
/**
* Transform the elevation from EPSG:4979 (WGS84 (EPSG:4326) + ellipsoidal height) to specified geoid model
* (by default EPSG:9707 (WGS84 + EGM96))
diff --git a/app/position/providers/bluetoothpositionprovider.cpp b/app/position/providers/bluetoothpositionprovider.cpp
index c96a235db..380c595fe 100644
--- a/app/position/providers/bluetoothpositionprovider.cpp
+++ b/app/position/providers/bluetoothpositionprovider.cpp
@@ -19,19 +19,8 @@
#include
#endif
-NmeaParser::NmeaParser() : QgsNmeaConnection( new QBluetoothSocket() )
-{
-}
-
-QgsGpsInformation NmeaParser::parseNmeaString( const QString &nmeaString )
-{
- mStringBuffer = nmeaString;
- processStringBuffer();
- return mLastGPSInformation;
-}
-
BluetoothPositionProvider::BluetoothPositionProvider( const QString &addr, const QString &name, PositionTransformer &positionTransformer, QObject *parent )
- : AbstractPositionProvider( addr, QStringLiteral( "external" ), name, positionTransformer, parent )
+ : AbstractPositionProvider( addr, QStringLiteral( "external_bt" ), name, positionTransformer, parent )
, mTargetAddress( addr )
{
mSocket = std::unique_ptr( new QBluetoothSocket( QBluetoothServiceInfo::RfcommProtocol ) );
diff --git a/app/position/providers/bluetoothpositionprovider.h b/app/position/providers/bluetoothpositionprovider.h
index 4670131da..8797c8ec1 100644
--- a/app/position/providers/bluetoothpositionprovider.h
+++ b/app/position/providers/bluetoothpositionprovider.h
@@ -10,29 +10,12 @@
#ifndef BLUETOOTHPOSITIONPROVIDER_H
#define BLUETOOTHPOSITIONPROVIDER_H
-#include "abstractpositionprovider.h"
-
-#include "qgsnmeaconnection.h"
-
#include
#include
#include
-/**
- * NmeaParser is a big hack how to reuse QGIS NmeaConnection function in order to (a) keep ownership of bluetooth
- * socket, (b) do not have multiple unique_ptrs holding the same pointer and to avoid some possible crashes.
- *
- * Note: This way of reusing makes the parser highly dependent on QgsNmeaConnection class and any change inside the class
- * can lead to misbehavior's. See implementation of QgsNmeaConnection and QgsGpsConnection for more details.
- */
-class NmeaParser : public QgsNmeaConnection
-{
- public:
- NmeaParser();
-
- // Takes nmea string and returns gps position
- QgsGpsInformation parseNmeaString( const QString &nmeaString );
-};
+#include "abstractpositionprovider.h"
+#include "nmeaparser.h"
/**
* BluetoothPositionProvider initiates connection to bluetooth device
diff --git a/app/position/providers/networkpositionprovider.cpp b/app/position/providers/networkpositionprovider.cpp
new file mode 100644
index 000000000..e7363d6ea
--- /dev/null
+++ b/app/position/providers/networkpositionprovider.cpp
@@ -0,0 +1,222 @@
+/***************************************************************************
+* *
+ * 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 "networkpositionprovider.h"
+
+#include
+#include
+
+static int ONE_SECOND_MS = 1000;
+
+NetworkPositionProvider::NetworkPositionProvider( const QString &addr, const QString &name, PositionTransformer &positionTransformer, QObject *parent )
+ : AbstractPositionProvider( addr, QStringLiteral( "external_ip" ), name, positionTransformer, parent ),
+ mSecondsLeftToReconnect( ReconnectDelay::ShortDelay / ONE_SECOND_MS )
+{
+ const QStringList targetAddress = addr.split( ":" );
+ mTargetAddress = targetAddress.at( 0 );
+ mTargetPort = targetAddress.at( 1 ).toInt();
+
+ mTcpSocket = std::make_unique();
+ mUdpSocket = std::make_unique();
+
+ connect( mTcpSocket.get(), &QTcpSocket::readyRead, this, &NetworkPositionProvider::positionUpdateReceived );
+ connect( mUdpSocket.get(), &QUdpSocket::readyRead, this, &NetworkPositionProvider::positionUpdateReceived );
+
+ connect( mTcpSocket.get(), &QTcpSocket::stateChanged, this, &NetworkPositionProvider::socketStateChanged );
+ connect( mUdpSocket.get(), &QUdpSocket::stateChanged, this, &NetworkPositionProvider::socketStateChanged );
+
+ mReconnectTimer.setSingleShot( false );
+ mReconnectTimer.setInterval( ONE_SECOND_MS );
+ connect( &mReconnectTimer, &QTimer::timeout, this, &NetworkPositionProvider::reconnectTimeout );
+ mUdpReconnectTimer.setSingleShot( true );
+ connect( &mUdpReconnectTimer, &QTimer::timeout, this, [this]
+ {
+ if ( mTcpSocket->state() != QAbstractSocket::ConnectedState )
+ {
+ setState( tr( "No connection" ), State::NoConnection );
+ startReconnectTimer();
+ // let's also invalidate current position since we no longer have connection
+ emit positionChanged( GeoPosition() );
+ }
+ } );
+
+ NetworkPositionProvider::startUpdates();
+}
+
+void NetworkPositionProvider::startUpdates()
+{
+ // TODO: QHostAddress doesn't support hostname lookup (QHostInfo does)
+ mTcpSocket->connectToHost( mTargetAddress, mTargetPort );
+ mUdpSocket->bind( QHostAddress::LocalHost, mTargetPort );
+ mUdpReconnectTimer.start( ReconnectDelay::ExtraLongDelay );
+}
+
+void NetworkPositionProvider::stopUpdates()
+{
+ if ( mTcpSocket->state() != QAbstractSocket::UnconnectedState && mTcpSocket->state() != QAbstractSocket::ClosingState )
+ {
+ mTcpSocket->disconnectFromHost();
+ }
+ if ( mUdpSocket->state() != QAbstractSocket::UnconnectedState && mUdpSocket->state() != QAbstractSocket::ClosingState )
+ {
+ mUdpSocket->disconnectFromHost();
+ }
+}
+
+void NetworkPositionProvider::closeProvider()
+{
+ mTcpSocket->close();
+ mUdpSocket->close();
+ if ( mTcpSocket ) mTcpSocket->disconnect();
+ if ( mUdpSocket ) mUdpSocket->disconnect();
+
+ mUdpReconnectTimer.stop();
+ mReconnectTimer.stop();
+}
+
+void NetworkPositionProvider::positionUpdateReceived()
+{
+ QAbstractSocket *socket = dynamic_cast( sender() );
+
+ // if udp is not connected to the host yet, connect
+ // this approach will let us use QIODevice functions for both sockets
+ if ( socket->socketType() == QAbstractSocket::UdpSocket && mUdpSocket->state() != QAbstractSocket::ConnectedState )
+ {
+ mUdpReconnectTimer.stop();
+
+ // if by any chance we showed wrong message in the status like "no connection", fix it here
+ // we know the connection is working because we just received data from the device
+ setState( tr( "Connected" ), State::Connected );
+
+ QHostAddress peerAddress;
+ int peerPort;
+ // process the incoming data as it will break the signal emitting if unprocessed
+ while ( mUdpSocket->hasPendingDatagrams() )
+ {
+ QNetworkDatagram datagram = mUdpSocket->receiveDatagram();
+ peerAddress = datagram.senderAddress();
+ peerPort = datagram.senderPort();
+ const QByteArray rawNmeaData = datagram.data();
+ const QString nmeaData( rawNmeaData );
+ const QgsGpsInformation gpsInfo = mNmeaParser.parseNmeaString( nmeaData );
+ GeoPosition transformedPosition = mPositionTransformer->processNetworkPosition( GeoPosition::fromQgsGpsInformation( gpsInfo ) );
+
+ emit positionChanged( transformedPosition );
+ }
+
+ // "connect" to peer if we are not already connecting
+ if ( mUdpSocket->state() != QAbstractSocket::ConnectedState && mUdpSocket->state() != QAbstractSocket::ConnectingState )
+ {
+ mUdpSocket->connectToHost( peerAddress.toString(), peerPort );
+ }
+ return;
+ }
+
+ // stop the UDP silence timer, we just received data
+ // kills the timer when the app was minimized, and we were able to reconnect in the meantime
+ if ( socket->socketType() == QAbstractSocket::UdpSocket )
+ {
+ mUdpReconnectTimer.stop();
+ }
+
+ const QByteArray rawNmeaData = socket->readAll();
+
+ if ( rawNmeaData.isEmpty() || !rawNmeaData.contains( '$' ) )
+ {
+ return;
+ }
+
+ // if by any chance we showed wrong message in the status like "no connection", fix it here
+ // we know the connection is working because we just received data from the device
+ setState( tr( "Connected" ), State::Connected );
+
+ const QString nmeaData( rawNmeaData );
+ const QgsGpsInformation gpsInfo = mNmeaParser.parseNmeaString( nmeaData );
+ GeoPosition transformedPosition = mPositionTransformer->processNetworkPosition( GeoPosition::fromQgsGpsInformation( gpsInfo ) );
+
+ emit positionChanged( GeoPosition::fromQgsGpsInformation( gpsInfo ) );
+}
+
+void NetworkPositionProvider::socketStateChanged( const QAbstractSocket::SocketState state )
+{
+ const QAbstractSocket *socket = dynamic_cast( sender() );
+
+ if ( state == QAbstractSocket::ConnectingState || state == QAbstractSocket::HostLookupState )
+ {
+ setState( tr( "Connecting to %1" ).arg( mProviderName ), State::Connecting );
+ }
+ // Only with TCP we can be sure in ConnectedState that we are connected, with UDP we wait until the first datagram arrives
+ else if ( state == QAbstractSocket::ConnectedState && socket->socketType() == QAbstractSocket::TcpSocket )
+ {
+ setState( tr( "Connected" ), State::Connected );
+ }
+ else if ( state == QAbstractSocket::UnconnectedState )
+ {
+ const bool isUdpSocketListening = mUdpSocket->state() == QAbstractSocket::ConnectedState || mUdpSocket->state() == QAbstractSocket::BoundState || mUdpReconnectTimer.isActive();
+ if ( socket->socketType() == QAbstractSocket::TcpSocket && !isUdpSocketListening && QApplication::applicationState() == Qt::ApplicationActive )
+ {
+ setState( tr( "No connection" ), State::NoConnection );
+ startReconnectTimer();
+ // let's also invalidate current position since we no longer have connection
+ emit positionChanged( GeoPosition() );
+ }
+ else if ( socket->socketType() == QAbstractSocket::UdpSocket && QApplication::applicationState() == Qt::ApplicationActive )
+ {
+ setState( tr( "No connection" ), State::NoConnection );
+ startReconnectTimer();
+ // let's also invalidate current position since we no longer have connection
+ emit positionChanged( GeoPosition() );
+ }
+ }
+}
+
+void NetworkPositionProvider::reconnectTimeout()
+{
+ if ( mSecondsLeftToReconnect <= 1 )
+ {
+ reconnect();
+ }
+ else
+ {
+ mSecondsLeftToReconnect--;
+ setState( tr( "No connection, reconnecting in (%1)" ).arg( mSecondsLeftToReconnect ), State::WaitingToReconnect );
+ mReconnectTimer.start();
+ }
+}
+
+QString NetworkPositionProvider::getIpAddress() const
+{
+ return mTargetAddress;
+}
+
+void NetworkPositionProvider::reconnect()
+{
+ mReconnectTimer.stop();
+
+ setState( tr( "Reconnecting" ), State::Connecting );
+
+ stopUpdates();
+ mReconnectTimer.stop();
+
+ startUpdates();
+}
+
+void NetworkPositionProvider::startReconnectTimer()
+{
+ mSecondsLeftToReconnect = mReconnectDelay / ONE_SECOND_MS;
+ setState( tr( "No connection, reconnecting in (%1)" ).arg( mSecondsLeftToReconnect ), State::WaitingToReconnect );
+
+ mReconnectTimer.start();
+
+ // first time do reconnect in short time, then each other in long time
+ if ( mReconnectDelay == NetworkPositionProvider::ShortDelay )
+ {
+ mReconnectDelay = NetworkPositionProvider::LongDelay;
+ }
+}
diff --git a/app/position/providers/networkpositionprovider.h b/app/position/providers/networkpositionprovider.h
new file mode 100644
index 000000000..3c87ddbbf
--- /dev/null
+++ b/app/position/providers/networkpositionprovider.h
@@ -0,0 +1,69 @@
+/***************************************************************************
+* *
+ * 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 NETWORKPOSITIONPROVIDER_H
+#define NETWORKPOSITIONPROVIDER_H
+
+#include
+#include
+#include
+
+#include "abstractpositionprovider.h"
+#include "nmeaparser.h"
+
+
+class NetworkPositionProvider : public AbstractPositionProvider
+{
+ Q_OBJECT
+
+ // signalizes in how many [ms] we will try to reconnect to GPS again
+ enum ReconnectDelay
+ {
+ ShortDelay = 3000, // 3 secs
+ LongDelay = 5000, // 5 secs
+ ExtraLongDelay = 10000 // 10 secs
+ };
+
+ public:
+ NetworkPositionProvider( const QString &addr, const QString &name, PositionTransformer &positionTransformer, QObject *parent = nullptr );
+
+ void startUpdates() override;
+ void stopUpdates() override;
+ void closeProvider() override;
+ Q_INVOKABLE QString getIpAddress() const; // returns the IP address we try to connect/ are connected to
+
+ public slots:
+ // processes the received nmea data and emits new position
+ void positionUpdateReceived();
+ // changes the provider state depending on the socket states
+ void socketStateChanged( QAbstractSocket::SocketState state );
+ // checks if enough time passed since last reconnect and triggers it if necessary
+ void reconnectTimeout();
+
+ private:
+ // trigger the reconnection flow for both sockets
+ void reconnect();
+ // start the reconnection timeout
+ void startReconnectTimer();
+
+ std::unique_ptr mTcpSocket;
+ std::unique_ptr mUdpSocket;
+
+ int mReconnectDelay = ReconnectDelay::ShortDelay; // in how many [ms] we will try to reconnect again
+ int mSecondsLeftToReconnect; // how many seconds are left to reconnect. Reconnects if less than or equal to one
+ QTimer mReconnectTimer; // timer that times out each second and lowers the mSecondsLeftToReconnect by one
+ QTimer mUdpReconnectTimer; // timer that times out after ExtraLongDelay and triggers reconnect
+
+ QString mTargetAddress; // IP address or hostname of the receiver
+ int mTargetPort; // active port of the receiver
+
+ NmeaParser mNmeaParser; // parser to decode received NMEA strings
+};
+#endif //NETWORKPOSITIONPROVIDER_H
diff --git a/app/position/providers/nmeaparser.cpp b/app/position/providers/nmeaparser.cpp
new file mode 100644
index 000000000..69a8f1015
--- /dev/null
+++ b/app/position/providers/nmeaparser.cpp
@@ -0,0 +1,23 @@
+/***************************************************************************
+* *
+ * 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 "nmeaparser.h"
+
+#include
+
+NmeaParser::NmeaParser() : QgsNmeaConnection( new QBuffer() )
+{
+}
+
+QgsGpsInformation NmeaParser::parseNmeaString( const QString &nmeaString )
+{
+ mStringBuffer = nmeaString;
+ processStringBuffer();
+ return mLastGPSInformation;
+}
\ No newline at end of file
diff --git a/app/position/providers/nmeaparser.h b/app/position/providers/nmeaparser.h
new file mode 100644
index 000000000..fc47d7e82
--- /dev/null
+++ b/app/position/providers/nmeaparser.h
@@ -0,0 +1,32 @@
+/***************************************************************************
+* *
+ * 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 NMEAPARSER_H
+#define NMEAPARSER_H
+
+#include
+
+/**
+ * NmeaParser is a big hack how to reuse QGIS NmeaConnection function in order to (a) keep ownership of bluetooth
+ * socket, (b) do not have multiple unique_ptrs holding the same pointer and to avoid some possible crashes.
+ *
+ * Note: This way of reusing makes the parser highly dependent on QgsNmeaConnection class and any change inside the class
+ * can lead to misbehavior's. See implementation of QgsNmeaConnection and QgsGpsConnection for more details.
+ */
+class NmeaParser : public QgsNmeaConnection
+{
+ public:
+ NmeaParser();
+
+// Takes nmea string and returns gps position
+ QgsGpsInformation parseNmeaString( const QString &nmeaString );
+};
+
+
+#endif //NMEAPARSER_H
\ No newline at end of file
diff --git a/app/position/providers/positionprovidersmodel.cpp b/app/position/providers/positionprovidersmodel.cpp
index e070e4576..c9a91b6ea 100644
--- a/app/position/providers/positionprovidersmodel.cpp
+++ b/app/position/providers/positionprovidersmodel.cpp
@@ -134,16 +134,17 @@ void PositionProvidersModel::removeProvider( const QString &providerId )
}
}
-void PositionProvidersModel::addProvider( const QString &name, const QString &providerId )
+void PositionProvidersModel::addProvider( const QString &name, const QString &providerId, const QString &providerType )
{
if ( providerId.isEmpty() )
return;
PositionProvider toAdd;
+ const QString deviceDesc = providerType == QStringLiteral( "external_bt" ) ? tr( " Bluetooth device" ) : tr( " Network device" );
toAdd.name = name;
toAdd.providerId = providerId;
- toAdd.description = providerId + " " + tr( " Bluetooth device" );
- toAdd.providerType = "external";
+ toAdd.description = providerId + " " + deviceDesc;
+ toAdd.providerType = providerType;
if ( mProviders.contains( toAdd ) )
return;
@@ -196,10 +197,12 @@ void PositionProvidersModel::setAppSettings( AppSettings *as )
}
PositionProvider provider;
+ const QString providerType = providerData[2].isNull() ? QStringLiteral( "external_bt" ) : QStringLiteral( "external_ip" );
+ const QString deviceDesc = providerType == QStringLiteral( "external_bt" ) ? tr( " Bluetooth device" ) : tr( " Network device" );
provider.name = providerData[0].toString();
provider.providerId = providerData[1].toString();
- provider.description = provider.providerId + " " + tr( "Bluetooth device" );
- provider.providerType = "external";
+ provider.description = provider.providerId + deviceDesc;
+ provider.providerType = providerType;
mProviders.append( provider );
}
@@ -218,8 +221,8 @@ QVariantList PositionProvidersModel::toVariantList() const
if ( mProviders[i].providerType == QStringLiteral( "internal" ) )
continue;
- QStringList a = { mProviders[i].name, mProviders[i].providerId };
- out.push_back( a );
+ QStringList provider = { mProviders[i].name, mProviders[i].providerId, mProviders[i].providerType };
+ out.push_back( provider );
}
return out;
diff --git a/app/position/providers/positionprovidersmodel.h b/app/position/providers/positionprovidersmodel.h
index dc24171fe..a05589504 100644
--- a/app/position/providers/positionprovidersmodel.h
+++ b/app/position/providers/positionprovidersmodel.h
@@ -67,7 +67,7 @@ class PositionProvidersModel : public QAbstractListModel
QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const override;
Q_INVOKABLE void removeProvider( const QString &providerId );
- Q_INVOKABLE void addProvider( const QString &providerName, const QString &providerId );
+ Q_INVOKABLE void addProvider( const QString &providerName, const QString &providerId, const QString &providerType );
AppSettings *appSettings() const;
void setAppSettings( AppSettings * );
diff --git a/app/qml/CMakeLists.txt b/app/qml/CMakeLists.txt
index d9662b019..a2697371d 100644
--- a/app/qml/CMakeLists.txt
+++ b/app/qml/CMakeLists.txt
@@ -119,9 +119,11 @@ set(MM_QML
form/editors/MMFormValueMapEditor.qml
form/editors/MMFormValueRelationEditor.qml
gps/MMAddPositionProviderDrawer.qml
- gps/MMBluetoothConnectionDrawer.qml
+ gps/MMExternalProviderConnectionDrawer.qml
gps/MMGpsDataDrawer.qml
+ gps/MMNetworkProviderDrawer.qml
gps/MMPositionProviderPage.qml
+ gps/MMProviderTypeDrawer.qml
gps/MMStakeoutDrawer.qml
gps/MMMeasureDrawer.qml
gps/MMSelectionDrawer.qml
diff --git a/app/qml/account/components/MMIconCheckBoxHorizontal.qml b/app/qml/account/components/MMIconCheckBoxHorizontal.qml
index cd3a02452..f9327bb18 100644
--- a/app/qml/account/components/MMIconCheckBoxHorizontal.qml
+++ b/app/qml/account/components/MMIconCheckBoxHorizontal.qml
@@ -17,21 +17,25 @@ CheckBox {
id: control
property string sourceIcon: ""
+ property string description: ""
+ property bool showBorder: false
property bool small: false
- height: (control.small ? 50 : 80) * __dp
+ height: (description !== "" ? 96 : (control.small ? 50 : 80)) * __dp
+
+ leftPadding: (description !== "" ? iconBgRectangle.x : 0) + iconBgRectangle.width + 30 * __dp
+ rightPadding: 20 * __dp
indicator: Rectangle {
id: iconBgRectangle
- width: (control.small ? 24 : 40) * __dp
- height: (control.small ? 24 : 40) * __dp
+ width: (control.small ? 24 : (control.description !== "" ? 50 : 40)) * __dp
+ height: (control.small ? 24 : (control.description !== "" ? 50 : 40)) * __dp
x: 20 * __dp
y: control.height / 2 - height / 2
radius: width / 2
color: control.checked ? __style.polarColor : __style.lightGreenColor
MMIcon {
- id: icon
size: control.small ? __style.icon16 : __style.icon24
anchors.centerIn: parent
source: control.sourceIcon
@@ -39,18 +43,49 @@ CheckBox {
}
}
- contentItem: Text {
- text: control.text
- font: __style.t3
- color: control.checked ? __style.polarColor : __style.nightColor
- elide: Text.ElideRight
- verticalAlignment: Text.AlignVCenter
- leftPadding: control.indicator.width + 30 * __dp
- rightPadding: 20 * __dp
+ contentItem: Item {
+ implicitWidth: titleText.implicitWidth
+
+ Text {
+ id: titleText
+ visible: control.description === ""
+ width: parent.width
+ height: parent.height
+ text: control.text
+ font: __style.t3
+ color: control.checked ? __style.polarColor : __style.nightColor
+ elide: Text.ElideRight
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ Column {
+ id: textColumn
+ visible: control.description !== ""
+ anchors.verticalCenter: parent.verticalCenter
+ width: parent.width
+ spacing: 10 * __dp
+
+ Text {
+ width: parent.width
+ text: control.text
+ font: __style.t3
+ color: control.checked ? __style.polarColor : __style.nightColor
+ elide: Text.ElideRight
+ }
+
+ Text {
+ width: parent.width
+ text: control.description
+ font: __style.p6
+ color: control.checked ? __style.polarColor : __style.nightColor
+ elide: Text.ElideRight
+ }
+ }
}
background: Rectangle {
radius: __style.radius12
- color: control.checked ? __style.forestColor: __style.polarColor
+ color: control.checked ? __style.forestColor : __style.polarColor
+ border.color: showBorder ? ( control.checked ? __style.transparentColor : __style.mediumGreenColor ) : __style.transparentColor
}
}
diff --git a/app/qml/gps/MMAddPositionProviderDrawer.qml b/app/qml/gps/MMAddPositionProviderDrawer.qml
index ab4771bcf..5531a52ca 100644
--- a/app/qml/gps/MMAddPositionProviderDrawer.qml
+++ b/app/qml/gps/MMAddPositionProviderDrawer.qml
@@ -103,16 +103,30 @@ MMComponents.MMListDrawer {
Column {
width: ListView.view.width
- spacing: 0
+ spacing: __style.spacing16
MMComponents.MMListSpacer { height: __style.margin40 }
+ Image {
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ source: __style.mmSymbolImage
+
+ width: 32 * __dp
+ height: 32 * __dp
+
+ fillMode: Image.PreserveAspectFit
+ }
+
MMComponents.MMText {
width: parent.width
- text: qsTr( "Looking for devices" ) + "..."
+ text: qsTr( "Looking for more devices" ) + "..."
font: __style.t3
+ color: __style.forestColor
+
+ horizontalAlignment: Text.AlignHCenter
}
}
}
diff --git a/app/qml/gps/MMBluetoothConnectionDrawer.qml b/app/qml/gps/MMExternalProviderConnectionDrawer.qml
similarity index 57%
rename from app/qml/gps/MMBluetoothConnectionDrawer.qml
rename to app/qml/gps/MMExternalProviderConnectionDrawer.qml
index 64d5c9af4..f3f0b9041 100644
--- a/app/qml/gps/MMBluetoothConnectionDrawer.qml
+++ b/app/qml/gps/MMExternalProviderConnectionDrawer.qml
@@ -19,24 +19,49 @@ import "../components" as MMComponents
MMComponents.MMDrawer {
id: root
+ property string providerType: ""
property var positionProvider: PositionKit.positionProvider
property string howToConnectGPSLink: __inputHelp.howToConnectGPSLink
property string titleText: {
- if ( rootstate.state === "working" )
- {
- if ( !root.positionProvider ) return ""
- if ( root.positionProvider.name() ) return qsTr( "Connecting to" ) + " " + root.positionProvider.name() + connectingSuffixAnimation
- return qsTr( "Connecting" ) + connectingSuffixAnimation
- }
- else if ( rootstate.state === "success" )
+ if ( root.providerType === "network" )
{
- return qsTr( "Connected" )
+ if ( rootstate.state === "working" )
+ {
+ if ( !root.positionProvider ) return ""
+ if ( root.positionProvider.getIpAddress() ) return qsTr( "Connecting to" ) + " " + root.positionProvider.getIpAddress() + connectingSuffixAnimation
+ return qsTr( "Connecting" ) + connectingSuffixAnimation
+ }
+ else if ( rootstate.state === "success" )
+ {
+ return qsTr( "Connected" )
+ }
+ else if ( rootstate.state === "fail" )
+ {
+ return qsTr( "Failed to connect to" ) + " " + ( root.positionProvider ? root.positionProvider.getIpAddress() : "" )
+ }
+ else return qsTr( "We were not able to connect to the specified IP address. Please try again later." )
}
- else
+ else if ( root.providerType === "bluetooth" )
{
- // either NoConnection or WaitingToReconnect
- return qsTr( "Failed to connect to" ) + " " + ( root.positionProvider ? root.positionProvider.name() : "" )
+ if ( rootstate.state === "working" )
+ {
+ if ( !root.positionProvider ) return ""
+ if ( root.positionProvider.name() ) return qsTr( "Connecting to" ) + " " + root.positionProvider.name() + connectingSuffixAnimation
+ return qsTr( "Connecting" ) + connectingSuffixAnimation
+ }
+ else if ( rootstate.state === "success" )
+ {
+ return qsTr( "Connected" )
+ }
+ else if ( rootstate.state === "fail" )
+ {
+ return qsTr( "Failed to connect to" ) + " " + ( root.positionProvider ? root.positionProvider.name() : "" )
+ }
+ else
+ {
+ if ( root.providerType === "bluetooth" ) return qsTr( "We were not able to connect to the specified device. Please make sure your device is powered on and can be connected to." )
+ }
}
}
@@ -45,7 +70,10 @@ MMComponents.MMDrawer {
property string descriptionText: {
if ( rootstate.state === "working" )
{
- return qsTr( "You might be asked to pair your device during this process." )
+ if ( root.providerType === "bluetooth" )
+ return qsTr( "You might be asked to pair your device during this process." )
+ else if ( root.providerType === "network" )
+ return qsTr( "This might take a while..." )
}
else if ( rootstate.state === "success" )
{
@@ -59,7 +87,10 @@ MMComponents.MMDrawer {
else
{
- return qsTr( "We were not able to connect to the specified device. Please make sure your device is powered on and can be connected to." )
+ if ( root.providerType === "bluetooth" )
+ return qsTr( "We were not able to connect to the specified device. Please make sure your device is powered on and can be connected to." )
+ else if ( root.providerType === "network" )
+ return qsTr( "We were not able to connect to the specified IP address. Please try again later." )
}
}
@@ -68,8 +99,12 @@ MMComponents.MMDrawer {
{
return __style.externalGpsRedImage
}
- else {
- return __style.externalGpsGreenImage
+ else
+ {
+ if ( root.providerType === "bluetooth" )
+ return __style.externalBluetoothGreenImage
+ else if ( root.providerType === "network" )
+ return __style.externalNetworkGreenImage
}
}
diff --git a/app/qml/gps/MMGpsDataDrawer.qml b/app/qml/gps/MMGpsDataDrawer.qml
index 1b018bbe3..ad4456e8c 100644
--- a/app/qml/gps/MMGpsDataDrawer.qml
+++ b/app/qml/gps/MMGpsDataDrawer.qml
@@ -73,7 +73,7 @@ MMComponents.MMDrawer {
width: parent.width / 2
- visible: PositionKit.positionProvider && PositionKit.positionProvider.type() === "external"
+ visible: __positionKit.positionProvider && __positionKit.positionProvider.type().includes("external")
title: qsTr( "Status" )
value: PositionKit.positionProvider ? PositionKit.positionProvider.stateMessage : ""
@@ -214,7 +214,7 @@ MMComponents.MMDrawer {
PositionKit.fix
}
- visible: PositionKit.positionProvider && PositionKit.positionProvider.type() === "external"
+ visible: __positionKit.positionProvider && __positionKit.positionProvider.type().includes("external")
alignmentRight: Positioner.index % 2 === 1
}
@@ -247,7 +247,7 @@ MMComponents.MMDrawer {
__inputUtils.formatNumber( PositionKit.hdop, 2 )
}
- visible: PositionKit.positionProvider && PositionKit.positionProvider.type() === "external"
+ visible: __positionKit.positionProvider && __positionKit.positionProvider.type().includes("external")
alignmentRight: Positioner.index % 2 === 1
}
@@ -264,7 +264,7 @@ MMComponents.MMDrawer {
__inputUtils.formatNumber( PositionKit.vdop, 2 )
}
- visible: PositionKit.positionProvider && PositionKit.positionProvider.type() === "external"
+ visible: __positionKit.positionProvider && __positionKit.positionProvider.type().includes("external")
alignmentRight: Positioner.index % 2 === 1
}
@@ -281,7 +281,7 @@ MMComponents.MMDrawer {
__inputUtils.formatNumber( PositionKit.pdop, 2 )
}
- visible: PositionKit.positionProvider && PositionKit.positionProvider.type() === "external"
+ visible: __positionKit.positionProvider && __positionKit.positionProvider.type().includes("external")
alignmentRight: Positioner.index % 2 === 1
}
@@ -329,6 +329,7 @@ MMComponents.MMDrawer {
}
__inputUtils.formatNumber( PositionKit.geoidSeparation, 2 ) + " m"
}
+ visible: __positionKit.positionProvider && __positionKit.positionProvider.type().includes("external")
alignmentRight: Positioner.index % 2 === 1
}
diff --git a/app/qml/gps/MMNetworkProviderDrawer.qml b/app/qml/gps/MMNetworkProviderDrawer.qml
new file mode 100644
index 000000000..d5632fdec
--- /dev/null
+++ b/app/qml/gps/MMNetworkProviderDrawer.qml
@@ -0,0 +1,119 @@
+/***************************************************************************
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+import QtQuick
+
+import "../components" as MMComponents
+import "../inputs" as MMInputs
+
+MMComponents.MMDrawer {
+ id: root
+
+ signal confirmed( string alias, string deviceAddress )
+
+ drawerHeader.title: qsTr( "Network connection" )
+ drawerHeader.titleFont: __style.t2
+
+ drawerContent: Column {
+ width: parent.width
+ spacing: __style.spacing20
+
+ MMComponents.MMText {
+ width: parent.width
+
+ text: qsTr( "External receivers can be connected via network in iOS devices. Some of the known devices that support network connection (TCP or UDP) are EOS and Emlid." )
+
+ font: __style.p5
+ color: __style.nightColor
+
+ wrapMode: Text.Wrap
+ horizontalAlignment: Text.AlignJustify
+ }
+
+ MMInputs.MMTextInput {
+ id: ipAddressInput
+
+ width: parent.width
+ textFieldBackground.color: __style.lightGreenColor
+
+ title: qsTr( "IP address" )
+ placeholderText: qsTr( "localhost" )
+
+ onTextEdited: errorMsg = ""
+ }
+
+ MMInputs.MMTextInput {
+ id: portInput
+
+ width: parent.width
+ textFieldBackground.color: __style.lightGreenColor
+
+ title: qsTr( "Port" )
+ placeholderText: qsTr( "1234" )
+
+ textField.inputMethodHints: Qt.ImhDigitsOnly
+
+ onTextEdited: errorMsg = ""
+ }
+
+ MMInputs.MMTextInput {
+ id: aliasInput
+
+ width: parent.width
+ textFieldBackground.color: __style.lightGreenColor
+
+ title: qsTr( "Receiver nickname (optional)" )
+ placeholderText: qsTr( "Green device" )
+ }
+
+ MMComponents.MMButton {
+ width: parent.width
+
+ text: qsTr( "Confirm" )
+
+ onClicked: {
+ const ip = ipAddressInput.text.trim()
+ const port = portInput.text.trim()
+
+ const ipv4Regex = /^((25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/
+ const hostnameRegex = /^[a-zA-Z0-9]([a-zA-Z0-9\-\.]*[a-zA-Z0-9])?$/
+
+ if ( ip === "" ) {
+ ipAddressInput.errorMsg = qsTr( "IP address is required" )
+ }
+ else if ( !ipv4Regex.test( ip ) && !hostnameRegex.test( ip ) ) {
+ ipAddressInput.errorMsg = qsTr( "Enter a valid IP address or hostname" )
+ }
+ else {
+ ipAddressInput.errorMsg = ""
+ }
+
+ const portNum = parseInt( port )
+ if ( port === "" ) {
+ portInput.errorMsg = qsTr( "Port is required" )
+ }
+ else if ( !/^\d+$/.test( port ) || portNum < 1 || portNum > 65535 ) {
+ portInput.errorMsg = qsTr( "Enter a valid port (1–65535)" )
+ }
+ else {
+ portInput.errorMsg = ""
+ }
+
+ if ( ipAddressInput.errorMsg !== "" || portInput.errorMsg !== "" ) {
+ return
+ }
+
+ const deviceAddress = ip + ":" + port
+
+ root.confirmed( aliasInput.text, deviceAddress )
+ root.close()
+ }
+ }
+ }
+}
diff --git a/app/qml/gps/MMPositionProviderPage.qml b/app/qml/gps/MMPositionProviderPage.qml
index c98d9a0c8..471e48ba6 100644
--- a/app/qml/gps/MMPositionProviderPage.qml
+++ b/app/qml/gps/MMPositionProviderPage.qml
@@ -8,8 +8,6 @@
***************************************************************************/
import QtQuick
-import QtQuick.Controls
-import QtQuick.Dialogs
import mm 1.0 as MM
import MMInput
@@ -33,8 +31,6 @@ MMComponents.MMPage {
property bool showTopTitle: visibleArea.yPosition * height > ( headerItem.contentHeight / 2 )
- visible: __haveBluetooth
-
width: parent.width
height: parent.height
@@ -94,7 +90,7 @@ MMComponents.MMPage {
if ( ListView.section === "internal" ) {
let ix = providersModel.index( index + 1, 0 )
let type = providersModel.data( ix, MM.PositionProvidersModel.ProviderType )
- if ( type === "external" ) return false
+ if ( type.includes( "external" ) ) return false
}
return true
@@ -135,10 +131,36 @@ MMComponents.MMPage {
text: qsTr( "Connect new receiver" )
- onClicked: bluetoothDiscoveryLoader.active = true
+ onClicked: {
+ if ( __haveBluetooth ) {
+ providerTypeDrawer.open()
+ }
+ else {
+ networkProviderDrawer.open()
+ }
+ }
+ }
+
+ MMProviderTypeDrawer {
+ id: providerTypeDrawer
+
+ onBluetoothSelected: bluetoothDiscoveryLoader.active = true
+ onNetworkSelected: networkProviderDrawer.open()
+ }
+
+ MMNetworkProviderDrawer {
+ id: networkProviderDrawer
+
+ onConfirmed: function( alias, deviceAddress ) {
+ PositionKit.positionProvider = PositionKit.constructProvider( "external_ip", deviceAddress, alias )
+ providersModel.addProvider( alias, deviceAddress, "external_ip" )
+ connectingDialogLoaderNetwork.open()
+ }
}
MMComponents.MMMessage {
+ id: infoMessage
+
visible: !listview.visible
width: parent.width
anchors.centerIn: parent
@@ -146,10 +168,10 @@ MMComponents.MMPage {
image: __style.externalGpsRedImage
title: qsTr( "Connecting to external receivers via bluetooth is not supported" )
description: qsTr( "This function is not available on iOS. " +
- "Your hardware vendor may provide a custom " +
- "app that connects to the receiver and sets position. " +
- "The app will still think it is the internal GPS of " +
- "your phone/tablet." )
+ "Your hardware vendor may provide a custom " +
+ "app that connects to the receiver and sets position. " +
+ "The app will still think it is the internal GPS of " +
+ "your phone/tablet." )
link: __inputHelp.howToConnectGPSLink
}
@@ -162,7 +184,7 @@ MMComponents.MMPage {
}
onRemoveProvider: {
- if (removeDialog.providerId === "") {
+ if ( removeDialog.providerId === "" ) {
close()
return
}
@@ -183,7 +205,23 @@ MMComponents.MMPage {
id: bluetoothDiscoveryLoader
active: false
- sourceComponent: bluetoothDiscoveryDrawerComponent
+ source: Qt.resolvedUrl( "MMAddPositionProviderDrawer.qml" )
+
+ onLoaded: item.open()
+ }
+
+ Connections {
+ target: bluetoothDiscoveryLoader.item
+
+ function onInitiatedConnectionTo( deviceAddress, deviceName ) {
+ PositionKit.positionProvider = PositionKit.constructProvider( "external_bt", deviceAddress, deviceName )
+ providersModel.addProvider( deviceName, deviceAddress, "external_bt" )
+ bluetoothDiscoveryLoader.item.list.model.discovering = false
+ bluetoothDiscoveryLoader.item.close()
+ connectingDialogLoader.open()
+ }
+
+ function onClosed() { bluetoothDiscoveryLoader.active = false }
}
Loader {
@@ -191,44 +229,49 @@ MMComponents.MMPage {
active: false
asynchronous: true
- sourceComponent: connectionToSavedProviderDialogComponent
+ source: Qt.resolvedUrl( "MMExternalProviderConnectionDrawer.qml" )
+
+ onLoaded: {
+ item.providerType = "bluetooth"
+ item.open()
+ }
function open() {
active = true
focus = true
}
}
- }
- Component {
- id: bluetoothDiscoveryDrawerComponent
+ Connections {
+ target: connectingDialogLoader.item
- MMAddPositionProviderDrawer {
- onInitiatedConnectionTo: function ( deviceAddress, deviceName ) {
- PositionKit.positionProvider = PositionKit.constructProvider( "external", deviceAddress, deviceName )
+ function onClosed() { connectingDialogLoader.active = false }
+ function onFailure() { PositionKit.positionProvider = PositionKit.constructProvider( "internal", "devicegps", "" ) }
+ }
- providersModel.addProvider( deviceName, deviceAddress )
- list.model.discovering = false
- close()
+ Loader {
+ id: connectingDialogLoaderNetwork
- connectingDialogLoader.open()
+ active: false
+ asynchronous: true
+ source: Qt.resolvedUrl( "MMExternalProviderConnectionDrawer.qml" )
+
+ onLoaded: {
+ item.providerType = "network"
+ item.open()
}
- onClosed: bluetoothDiscoveryLoader.active = false
- Component.onCompleted: open()
+ function open() {
+ active = true
+ focus = true
+ }
}
- }
-
- Component {
- id: connectionToSavedProviderDialogComponent
- MMBluetoothConnectionDrawer {
- onClosed: connectingDialogLoader.active = false
+ Connections {
+ target: connectingDialogLoaderNetwork.item
- // revert position provider back to internal provider
- onFailure: PositionKit.positionProvider = PositionKit.constructProvider( "internal", "devicegps", "" )
-
- Component.onCompleted: open()
+ function onClosed() { connectingDialogLoaderNetwork.active = false }
+ function onFailure() { PositionKit.positionProvider = PositionKit.constructProvider( "internal", "devicegps", "" ) }
}
}
@@ -239,7 +282,7 @@ MMComponents.MMPage {
}
function constructProvider( type, id, name ) {
- if ( type === "external" ) {
+ if ( type === "external_bt" ) {
// Is bluetooth turned on?
if ( !__inputUtils.isBluetoothTurnedOn() ) {
__inputUtils.turnBluetoothOn()
@@ -253,8 +296,11 @@ MMComponents.MMPage {
PositionKit.positionProvider = PositionKit.constructProvider( type, id, name )
- if ( type === "external" ) {
+ if ( type === "external_bt" ) {
connectingDialogLoader.open()
}
+ else if ( type === "external_ip" ) {
+ connectingDialogLoaderNetwork.open()
+ }
}
}
diff --git a/app/qml/gps/MMProviderTypeDrawer.qml b/app/qml/gps/MMProviderTypeDrawer.qml
new file mode 100644
index 000000000..af281397e
--- /dev/null
+++ b/app/qml/gps/MMProviderTypeDrawer.qml
@@ -0,0 +1,102 @@
+/***************************************************************************
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+import QtQuick
+
+import "../components" as MMComponents
+import "../account/components" as MMAccountComponents
+
+MMComponents.MMListDrawer {
+ id: root
+
+ property string selectedProviderType: ""
+
+ signal bluetoothSelected()
+ signal networkSelected()
+
+ drawerHeader.title: qsTr( "Connect new receiver" )
+ drawerHeader.titleFont: __style.t2
+
+ onOpened: root.selectedProviderType = ""
+
+ list.model: ListModel {
+ id: providerTypeModel
+
+ Component.onCompleted: {
+ providerTypeModel.append( [
+ { name: qsTr( "Bluetooth" ), description: qsTr( "Connect via Bluetooth" ), type: "bluetooth", icon: __style.bluetoothIcon },
+ { name: qsTr( "Network" ), description: qsTr( "Connect via IP address" ), type: "network", icon: __style.networkIcon }
+ ] )
+ }
+ }
+
+ list.header: MMComponents.MMText {
+ width: ListView.view.width
+
+ text: qsTr( "This function is not available on iOS. Your hardware vendor may provide a custom app that connects to the receiver and sets position. Mergin Maps will still think it is the internal GPS of your phone/tablet." )
+
+ font: __style.p5
+ color: __style.nightColor
+
+ wrapMode: Text.Wrap
+ bottomPadding: __style.margin16
+ }
+
+ list.delegate: Item {
+ width: ListView.view.width
+ height: checkbox.height + __style.margin12
+
+ MMAccountComponents.MMIconCheckBoxHorizontal {
+ id: checkbox
+
+ width: parent.width
+ showBorder: true
+ sourceIcon: model.icon
+ text: model.name
+ description: model.description
+ checked: root.selectedProviderType === model.type
+
+ onClicked: {
+ if ( root.selectedProviderType === model.type ) {
+ root.selectedProviderType = ""
+ }
+ else {
+ root.selectedProviderType = model.type
+ }
+ }
+ }
+ }
+
+ list.footer: Item {
+ width: ListView.view.width
+ height: continueButton.height + __style.margin20
+
+ MMComponents.MMButton {
+ id: continueButton
+
+ width: parent.width
+ anchors.top: parent.top
+ anchors.topMargin: __style.margin8
+
+ text: qsTr( "Continue" )
+ enabled: root.selectedProviderType !== ""
+
+ onClicked: {
+ root.close()
+
+ if ( root.selectedProviderType === "bluetooth" ) {
+ root.bluetoothSelected()
+ }
+ else if ( root.selectedProviderType === "network" ) {
+ root.networkSelected()
+ }
+ }
+ }
+ }
+}
diff --git a/app/qml/map/MMMapController.qml b/app/qml/map/MMMapController.qml
index ae892281d..6766d3f32 100644
--- a/app/qml/map/MMMapController.qml
+++ b/app/qml/map/MMMapController.qml
@@ -679,7 +679,7 @@ Item {
visible: {
if ( root.mapExtentOffset > 0 && root.state !== "stakeout" ) return false
- if ( PositionKit.positionProvider && PositionKit.positionProvider.type() === "external" ) {
+ if ( __positionKit.positionProvider && __positionKit.positionProvider.type().includes("external") ) {
// for external receivers we want to show gps panel and accuracy button
// even when the GPS receiver is not sending position data
return true
@@ -699,7 +699,7 @@ Item {
{
return ""
}
- else if ( PositionKit.positionProvider.type() === "external" )
+ else if ( __positionKit.positionProvider.type().includes("external") )
{
if ( PositionKit.positionProvider.state === MM.PositionProvider.Connecting )
{
diff --git a/app/test/testposition.cpp b/app/test/testposition.cpp
index 6c0bdba26..846c4c227 100644
--- a/app/test/testposition.cpp
+++ b/app/test/testposition.cpp
@@ -106,8 +106,8 @@ void TestPosition::testBluetoothProviderConnection()
QCOMPARE( "testBluetoothProvider", pkProvider->name() );
QCOMPARE( "AA:AA:AA:AA:00:00", btProvider->id() );
QCOMPARE( "AA:AA:AA:AA:00:00", pkProvider->id() );
- QCOMPARE( "external", btProvider->type() );
- QCOMPARE( "external", pkProvider->type() );
+ QCOMPARE( "external_bt", btProvider->type() );
+ QCOMPARE( "external_bt", pkProvider->type() );
//
// let's continue with BT instance,
@@ -257,11 +257,11 @@ void TestPosition::testPositionProviderKeysInSettings()
rawSettings.remove( AppSettings::POSITION_PROVIDERS_GROUP ); // make sure nothing is there from previous tests
#ifdef HAVE_BLUETOOTH
- positionKit->setPositionProvider( positionKit->constructProvider( "external", "AA:BB:CC:DD:EE:FF", "testProviderA" ) );
+ positionKit->setPositionProvider( positionKit->constructProvider( "external_bt", "AA:BB:CC:DD:EE:FF", "testProviderA" ) );
QCOMPARE( positionKit->positionProvider()->id(), "AA:BB:CC:DD:EE:FF" );
QCOMPARE( positionKit->positionProvider()->name(), "testProviderA" );
- QCOMPARE( positionKit->positionProvider()->type(), "external" );
+ QCOMPARE( positionKit->positionProvider()->type(), "external_bt" );
QCOMPARE( rawSettings.value( CoreUtils::QSETTINGS_APP_GROUP_NAME + "/activePositionProviderId" ).toString(), "AA:BB:CC:DD:EE:FF" );
#endif
@@ -279,20 +279,28 @@ void TestPosition::testPositionProviderKeysInSettings()
QCOMPARE( providersModel.data( providersModel.index( 1 ), PositionProvidersModel::ProviderId ), "simulated" );
providersModel.setAppSettings( &appSettings );
- providersModel.addProvider( "testProviderB", "AA:00:11:22:23:44" );
+ providersModel.addProvider( "testProviderB", "AA:00:11:22:23:44", "external_bt" );
+ providersModel.addProvider( "testProviderC", "localhost:9000", "external_ip" );
- // app settings should have one saved provider - testProviderB
+ // app settings should have two saved providers - testProviderB & testProviderC
QVariantList providers = appSettings.savedPositionProviders();
- QCOMPARE( providers.count(), 1 ); // we have one (external) provider
- QCOMPARE( providers.at( 0 ).toList().count(), 2 ); // the provider has two properties
+ QCOMPARE( providers.count(), 2 ); // we have two (external) providers
+ QCOMPARE( providers.at( 0 ).toList().count(), 3 ); // the provider has two properties
QVariantList providerData = providers.at( 0 ).toList();
QCOMPARE( providerData.at( 0 ).toString(), "testProviderB" );
QCOMPARE( providerData.at( 1 ).toString(), "AA:00:11:22:23:44" );
+ QCOMPARE( providerData.at( 2 ).toString(), "external_bt" );
+
+ providerData = providers.at( 1 ).toList();
+ QCOMPARE( providerData.at( 0 ).toString(), "testProviderC" );
+ QCOMPARE( providerData.at( 1 ).toString(), "localhost:9000" );
+ QCOMPARE( providerData.at( 2 ).toString(), "external_ip" );
// remove that provider
providersModel.removeProvider( "AA:00:11:22:23:44" );
+ providersModel.removeProvider( "localhost:9000" );
providers = appSettings.savedPositionProviders();
@@ -762,6 +770,73 @@ void TestPosition::testPositionTransformerInternalDesktopPosition()
QVERIFY( qgsDoubleNear( newPosition.elevation, 171.3 ) );
}
+void TestPosition::testPositionTransformerNetworkPosition()
+{
+// prepare position transformers
+ // WGS84 + ellipsoid
+ QgsCoordinateReferenceSystem ellipsoidHeightCrs = QgsCoordinateReferenceSystem::fromEpsgId( 4979 );
+ // WGS84 + EGM96
+ QgsCoordinateReferenceSystem geoidHeightCrs = QgsCoordinateReferenceSystem::fromEpsgId( 9707 );
+ PositionTransformer passThroughTransformer( ellipsoidHeightCrs, geoidHeightCrs, true, QgsCoordinateTransformContext() );
+ PositionTransformer positionTransformer( ellipsoidHeightCrs, geoidHeightCrs, false, QgsCoordinateTransformContext() );
+
+#ifdef HAVE_BLUETOOTH
+ // mini file contains only minimal info like position and date
+ QString miniNmeaPositionFilePath = TestUtils::testDataDir() + "/position/nmea_petrzalka_mini.txt";
+ QFile miniNmeaFile( miniNmeaPositionFilePath );
+ miniNmeaFile.open( QFile::ReadOnly );
+
+ QVERIFY( miniNmeaFile.isOpen() );
+
+ NmeaParser parser;
+ QgsGpsInformation position = parser.parseNmeaString( miniNmeaFile.readAll() );
+ GeoPosition geoPosition = GeoPosition::fromQgsGpsInformation( position );
+
+ QVERIFY( qgsDoubleNear( geoPosition.latitude, 48.10305 ) );
+ QVERIFY( qgsDoubleNear( geoPosition.longitude, 17.1064 ) );
+ QCOMPARE( geoPosition.elevation, 171.3 );
+ QCOMPARE( geoPosition.elevation_diff, std::numeric_limits::quiet_NaN() );
+#else
+ GeoPosition geoPosition;
+ geoPosition.latitude = 48.10305;
+ geoPosition.longitude = 17.1064;
+ geoPosition.elevation = 171.3;
+#endif
+
+ // transform with pass through disabled and missing elevation separation
+ GeoPosition newPosition = positionTransformer.processNetworkPosition( geoPosition );
+
+ QVERIFY( qgsDoubleNear( newPosition.latitude, 48.10305 ) );
+ QVERIFY( qgsDoubleNear( newPosition.longitude, 17.1064 ) );
+ QCOMPARE( newPosition.elevation, 171.3 );
+ QCOMPARE( newPosition.elevation_diff, std::numeric_limits::quiet_NaN() );
+
+ // transform with pass through enabled and missing elevation separation
+ newPosition = passThroughTransformer.processNetworkPosition( geoPosition );
+
+ QVERIFY( qgsDoubleNear( newPosition.latitude, 48.10305 ) );
+ QVERIFY( qgsDoubleNear( newPosition.longitude, 17.1064 ) );
+ QCOMPARE( newPosition.elevation, 171.3 );
+ QCOMPARE( newPosition.elevation_diff, std::numeric_limits::quiet_NaN() );
+
+ // transform with pass through enabled and elevation separation
+ geoPosition.elevation_diff = 40;
+ newPosition = passThroughTransformer.processNetworkPosition( geoPosition );
+
+ QVERIFY( qgsDoubleNear( newPosition.latitude, 48.10305 ) );
+ QVERIFY( qgsDoubleNear( newPosition.longitude, 17.1064 ) );
+ QCOMPARE( newPosition.elevation, 171.3 );
+ QCOMPARE( newPosition.elevation_diff, 40 );
+
+ // transform with pass through disabled and elevation separation
+ newPosition = positionTransformer.processNetworkPosition( geoPosition );
+
+ QVERIFY( qgsDoubleNear( newPosition.latitude, 48.10305 ) );
+ QVERIFY( qgsDoubleNear( newPosition.longitude, 17.1064 ) );
+ QVERIFY( qgsDoubleNear( newPosition.elevation, 167.53574931171875 ) );
+ QVERIFY( qgsDoubleNear( newPosition.elevation_diff, 43.764250688281265 ) );
+}
+
void TestPosition::testPositionTransformerSimulatedPosition()
{
// prepare position transformers
diff --git a/app/test/testposition.h b/app/test/testposition.h
index 3636b89c3..40ffd45c6 100644
--- a/app/test/testposition.h
+++ b/app/test/testposition.h
@@ -44,6 +44,7 @@ class TestPosition: public QObject
void testPositionTransformerInternalAndroidPosition();
void testPositionTransformerInternalIosPosition();
void testPositionTransformerInternalDesktopPosition();
+ void testPositionTransformerNetworkPosition();
void testPositionTransformerSimulatedPosition();
private:
diff --git a/app/test/testvariablesmanager.cpp b/app/test/testvariablesmanager.cpp
index e7bda0498..cd191c431 100644
--- a/app/test/testvariablesmanager.cpp
+++ b/app/test/testvariablesmanager.cpp
@@ -84,7 +84,7 @@ void TestVariablesManager::testPositionVariables()
evaluateExpression( QStringLiteral( "@position_gps_antenna_height" ), QStringLiteral( "0.000" ), &context );
evaluateExpression( QStringLiteral( "@position_provider_address" ), QStringLiteral( "AA:AA:FF:AA:00:10" ), &context );
evaluateExpression( QStringLiteral( "@position_provider_name" ), QStringLiteral( "testBluetoothProvider" ), &context );
- evaluateExpression( QStringLiteral( "@position_provider_type" ), QStringLiteral( "external" ), &context );
+ evaluateExpression( QStringLiteral( "@position_provider_type" ), QStringLiteral( "external_bt" ), &context );
mAppSettings->setGpsAntennaHeight( 1.6784 );
pos.verticalSpeed = 1.345;
diff --git a/gallery/positionkit.h b/gallery/positionkit.h
index 059a3e737..bab5f6dfc 100644
--- a/gallery/positionkit.h
+++ b/gallery/positionkit.h
@@ -61,7 +61,7 @@ class PositionKit : public QObject
private:
QString pProviderName = "Gps Source is ok!";
- QString pProviderType = "external";
+ QString pProviderType = "external_bt";
QString pProviderMessage = "Connected";
QString pStateMessage = "Message";
QString pLastRead = "17:19:08 CEST";
diff --git a/gallery/qml.qrc b/gallery/qml.qrc
index 9d82ad2d8..a8a055cbe 100644
--- a/gallery/qml.qrc
+++ b/gallery/qml.qrc
@@ -140,7 +140,7 @@
../app/qml/settings/components/MMSettingsInput.qml
../app/qml/settings/components/MMSettingsDropdown.qml
../app/qml/settings/components/MMSettingsSwitch.qml
- ../app/qml/gps/MMBluetoothConnectionDrawer.qml
+ ../app/qml/gps/MMExternalProviderConnectionDrawer.qml
../app/qml/gps/MMGpsDataDrawer.qml
../app/qml/gps/components/MMGpsDataText.qml
../app/qml/dialogs/MMSyncFailedDialog.qml
diff --git a/gallery/qml/pages/ChecksPage.qml b/gallery/qml/pages/ChecksPage.qml
index 9e097a4b1..11afacbb4 100644
--- a/gallery/qml/pages/ChecksPage.qml
+++ b/gallery/qml/pages/ChecksPage.qml
@@ -90,9 +90,11 @@ Column {
MMAccountComponents.MMIconCheckBoxHorizontal {
checked: true
+ width: 300 * __dp
sourceIcon: __style.redditIcon
text: "Reddit"
- small: true
+ description: "This is a small description to check the functionality of this component"
+ small: false
}
}
}
diff --git a/gallery/qml/pages/DrawerPage.qml b/gallery/qml/pages/DrawerPage.qml
index e3400a423..47a0aab1f 100644
--- a/gallery/qml/pages/DrawerPage.qml
+++ b/gallery/qml/pages/DrawerPage.qml
@@ -59,7 +59,7 @@ Page {
}
Button {
- text: "MMBluetoothConnectionDrawer"
+ text: "MMExternalProviderConnectionDrawer"
onClicked: {
bluetoothConnectionDrawer.positionProvider.state = PositionProvider.Connecting
bluetoothConnectionTimer.start()
@@ -197,8 +197,9 @@ Page {
}
}
- MMBluetoothConnectionDrawer {
+ MMExternalProviderConnectionDrawer {
id: bluetoothConnectionDrawer
+ providerType: "bluetooth"
howToConnectGPSLink: "www.merginmaps.com"
positionProvider: QtObject {
diff --git a/gallery/qml/pages/ImagesPage.qml b/gallery/qml/pages/ImagesPage.qml
index ed931d11b..59bd68133 100644
--- a/gallery/qml/pages/ImagesPage.qml
+++ b/gallery/qml/pages/ImagesPage.qml
@@ -38,7 +38,8 @@ ScrollView {
Column { Image { source: __style.positionTrackingRunningImage } Text { text: "positionTrackingRunningImage" } }
Column { Image { source: __style.noMapThemesImage } Text { text: "noMapThemesImage" } }
Column { Image { source: __style.syncImage } Text { text: "syncImage" } }
- Column { Image { source: __style.externalGpsGreenImage } Text { text: "externalGpsGreenImage" } }
+ Column { Image { source: __style.externalBluetoothGreenImage } Text { text: "externalBluetoothGreenImage" } }
+ Column { Image { source: __style.externalNetworkGreenImage } Text { text: "externalNetworkGreenImage" } }
Column { Image { source: __style.externalGpsRedImage } Text { text: "externalGpsRedImage" } }
Column { Image { source: __style.reachedDataLimitImage } Text { text: "reachedDataLimitImage" } }
Column { Image { source: __style.positiveMMSymbolImage } Text { text: "positiveMMSymbolImage" } }