diff --git a/dist/changes-5.9.4 b/dist/changes-5.9.4 new file mode 100644 index 0000000000000000000000000000000000000000..639312985d7f4f577dbb9b7cfb3870993c274e35 --- /dev/null +++ b/dist/changes-5.9.4 @@ -0,0 +1,31 @@ +Qt 5.9.4 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.9.0. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +http://doc.qt.io/qt-5/index.html + +The Qt version 5.9 series is binary compatible with the 5.8.x series. +Applications compiled for 5.8 will continue to run with 5.9. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Qt 5.9.4 Changes * +**************************************************************************** + +QtLocation +---------- + + - [QTBUG-63013] Upgraded Mapbox GL to fix SQLite query binding. + - [QTBUG-58589] Added plugin dependencies to the mapviewer example. + - [QTBUG-64632] Upgraded Mapbox GL to fix remove GLES limitations on Windows. + This upgrade also fixed building Mapbox GL with MinGW. + - [QTBUG-57147] Fixed license headers. diff --git a/examples/location/mapviewer/mapviewer.pro b/examples/location/mapviewer/mapviewer.pro index d49258a24e2ca178486c55da5725353a034e46dc..8e3daf8191baa584e61d1f9869c5c9b1b2950ad0 100644 --- a/examples/location/mapviewer/mapviewer.pro +++ b/examples/location/mapviewer/mapviewer.pro @@ -2,7 +2,6 @@ TARGET = qml_location_mapviewer TEMPLATE = app QT += qml network quick positioning location -CONFIG += qtquickcompiler SOURCES += main.cpp # Workaround for QTBUG-38735 diff --git a/src/3rdparty/earcut/earcut.hpp b/src/3rdparty/earcut/earcut.hpp index 287be0281873184cea455e060fb49243c21c815f..ba3fb17a486e06e95c68140e455c5968aa41c6e3 100644 --- a/src/3rdparty/earcut/earcut.hpp +++ b/src/3rdparty/earcut/earcut.hpp @@ -9,7 +9,7 @@ #include <memory> #include <vector> -namespace mapbox { +namespace qt_mapbox { namespace util { @@ -771,7 +771,7 @@ void Earcut<N>::removeNode(Node* p) { template <typename N = uint32_t, typename Polygon> std::vector<N> earcut(const Polygon& poly) { - mapbox::detail::Earcut<N> earcut; + qt_mapbox::detail::Earcut<N> earcut; earcut(poly); return earcut.indices; } diff --git a/src/3rdparty/mapbox-gl-native b/src/3rdparty/mapbox-gl-native index 8c1be4ec01ef46bf453856531ebf53b48ce3dbe7..572822c8ca15be190b43afbf7f91d132e988bf21 160000 --- a/src/3rdparty/mapbox-gl-native +++ b/src/3rdparty/mapbox-gl-native @@ -1 +1 @@ -Subproject commit 8c1be4ec01ef46bf453856531ebf53b48ce3dbe7 +Subproject commit 572822c8ca15be190b43afbf7f91d132e988bf21 diff --git a/src/location/declarativemaps/qdeclarativegeocodemodel.cpp b/src/location/declarativemaps/qdeclarativegeocodemodel.cpp index b64b2545add8cb6f38fa6dd2da82022664fd8703..9a9a2555936efaa5680d2b1a5c3dc990a8ffa27c 100644 --- a/src/location/declarativemaps/qdeclarativegeocodemodel.cpp +++ b/src/location/declarativemaps/qdeclarativegeocodemodel.cpp @@ -385,12 +385,13 @@ void QDeclarativeGeocodeModel::geocodeFinished(QGeoCodeReply *reply) { if (reply != reply_ || reply->error() != QGeoCodeReply::NoError) return; + + reply->deleteLater(); + reply_ = 0; int oldCount = declarativeLocations_.count(); setLocations(reply->locations()); setError(NoError, QString()); setStatus(QDeclarativeGeocodeModel::Ready); - reply->deleteLater(); - reply_ = 0; emit locationsChanged(); if (oldCount != declarativeLocations_.count()) emit countChanged(); @@ -405,7 +406,9 @@ void QDeclarativeGeocodeModel::geocodeError(QGeoCodeReply *reply, { if (reply != reply_) return; - Q_UNUSED(error); + + reply->deleteLater(); + reply_ = 0; int oldCount = declarativeLocations_.count(); if (oldCount > 0) { // Reset the model @@ -415,8 +418,6 @@ void QDeclarativeGeocodeModel::geocodeError(QGeoCodeReply *reply, } setError(static_cast<QDeclarativeGeocodeModel::GeocodeError>(error), errorString); setStatus(QDeclarativeGeocodeModel::Error); - reply->deleteLater(); - reply_ = 0; } /*! diff --git a/src/location/declarativemaps/qdeclarativegeomap.cpp b/src/location/declarativemaps/qdeclarativegeomap.cpp index 1bf5ea87b4b6097bce273e3ad553bb8c8099a43f..5c916fb2f200e87f40124c3f41d4bb7ae4cf2297 100644 --- a/src/location/declarativemaps/qdeclarativegeomap.cpp +++ b/src/location/declarativemaps/qdeclarativegeomap.cpp @@ -52,6 +52,7 @@ #include <QtQuick/QSGRectangleNode> #include <QtQuick/private/qquickwindow_p.h> #include <QtQml/qqmlinfo.h> +#include <QtQuick/private/qquickitem_p.h> #include <cmath> #ifndef M_PI @@ -199,8 +200,6 @@ QDeclarativeGeoMap::QDeclarativeGeoMap(QQuickItem *parent) setFlags(QQuickItem::ItemHasContents | QQuickItem::ItemClipsChildrenToShape); setFiltersChildMouseEvents(true); - connect(this, SIGNAL(childrenChanged()), this, SLOT(onMapChildrenChanged()), Qt::QueuedConnection); - m_activeMapType = new QDeclarativeGeoMapType(QGeoMapType(QGeoMapType::NoMap, tr("No Map"), tr("No Map"), @@ -266,7 +265,8 @@ QDeclarativeGeoMap::~QDeclarativeGeoMap() } m_mapItemGroups.clear(); - delete m_copyrights.data(); + if (m_copyrights.data()) + delete m_copyrights.data(); m_copyrights.clear(); for (auto obj: qAsConst(m_pendingMapObjects)) @@ -275,71 +275,6 @@ QDeclarativeGeoMap::~QDeclarativeGeoMap() delete m_map; // map objects get reset here } -/*! - \internal -*/ -void QDeclarativeGeoMap::onMapChildrenChanged() -{ - if (!m_componentCompleted || !m_map) - return; - - int maxChildZ = 0; - QObjectList kids = children(); - bool foundCopyrights = false; - - for (int i = 0; i < kids.size(); ++i) { - QDeclarativeGeoMapCopyrightNotice *copyrights = qobject_cast<QDeclarativeGeoMapCopyrightNotice *>(kids.at(i)); - if (copyrights) { - foundCopyrights = true; - } else { - QDeclarativeGeoMapItemBase *mapItem = qobject_cast<QDeclarativeGeoMapItemBase *>(kids.at(i)); - if (mapItem) { - if (mapItem->z() > maxChildZ) - maxChildZ = mapItem->z(); - } - } - } - - QDeclarativeGeoMapCopyrightNotice *copyrights = m_copyrights.data(); - // if copyrights object not found within the map's children - if (!foundCopyrights) { - // if copyrights object was deleted! - if (!copyrights) { - // create a new one and set its parent, re-assign it to the weak pointer, then connect the copyrights-change signal - m_copyrights = new QDeclarativeGeoMapCopyrightNotice(this); - m_copyrights->onCopyrightsStyleSheetChanged(m_map->copyrightsStyleSheet()); - - copyrights = m_copyrights.data(); - - connect(m_map, SIGNAL(copyrightsChanged(QImage)), - copyrights, SLOT(copyrightsChanged(QImage))); - connect(m_map, SIGNAL(copyrightsChanged(QImage)), - this, SIGNAL(copyrightsChanged(QImage))); - - connect(m_map, SIGNAL(copyrightsChanged(QString)), - copyrights, SLOT(copyrightsChanged(QString))); - connect(m_map, SIGNAL(copyrightsChanged(QString)), - this, SIGNAL(copyrightsChanged(QString))); - - connect(m_map, SIGNAL(copyrightsStyleSheetChanged(QString)), - copyrights, SLOT(onCopyrightsStyleSheetChanged(QString))); - - connect(copyrights, SIGNAL(linkActivated(QString)), - this, SIGNAL(copyrightLinkActivated(QString))); - - // set visibility of copyright notice - copyrights->setCopyrightsVisible(m_copyrightsVisible); - - } else { - // just re-set its parent. - copyrights->setParent(this); - } - } - - // put the copyrights notice object at the highest z order - copyrights->setCopyrightsZ(maxChildZ + 1); -} - static QDeclarativeGeoMapType *findMapType(const QList<QDeclarativeGeoMapType *> &types, const QGeoMapType &type) { for (int i = 0; i < types.size(); ++i) @@ -456,7 +391,7 @@ void QDeclarativeGeoMap::initialize() emit mapReadyChanged(true); - if (m_copyrights) + if (m_copyrights) // To not update during initialize() update(); } @@ -757,7 +692,6 @@ void QDeclarativeGeoMap::onCameraCapabilitiesChanged(const QGeoCameraCapabilitie } } - /*! \internal this function will only be ever called once @@ -769,6 +703,12 @@ void QDeclarativeGeoMap::mappingManagerInitialized() if (!m_map) return; + /* COPY NOTICE SETUP */ + m_copyrights = new QDeclarativeGeoMapCopyrightNotice(this); + m_copyrights->setCopyrightsZ(m_maxChildZ + 1); + m_copyrights->setCopyrightsVisible(m_copyrightsVisible); + m_copyrights->setMapSource(this); + m_gestureArea->setMap(m_map); QList<QGeoMapType> types = m_mappingManager->supportedMapTypes(); @@ -817,40 +757,26 @@ void QDeclarativeGeoMap::mappingManagerInitialized() QOverload<const QImage &>::of(&QGeoMap::copyrightsChanged), [©rightImage](const QImage ©){ copyrightImage = copy; }); m_map->setViewportSize(QSize(width(), height())); - initialize(); + initialize(); // This emits the caught signals above QObject::disconnect(copyrightStringCatcherConnection); QObject::disconnect(copyrightImageCatcherConnection); } - m_copyrights = new QDeclarativeGeoMapCopyrightNotice(this); - m_copyrights->onCopyrightsStyleSheetChanged(m_map->copyrightsStyleSheet()); - connect(m_map, SIGNAL(copyrightsChanged(QImage)), - m_copyrights.data(), SLOT(copyrightsChanged(QImage))); + /* COPYRIGHT SIGNALS REWIRING */ connect(m_map, SIGNAL(copyrightsChanged(QImage)), this, SIGNAL(copyrightsChanged(QImage))); - - connect(m_map, SIGNAL(copyrightsChanged(QString)), - m_copyrights.data(), SLOT(copyrightsChanged(QString))); connect(m_map, SIGNAL(copyrightsChanged(QString)), this, SIGNAL(copyrightsChanged(QString))); - if (!copyrightString.isEmpty()) emit m_map->copyrightsChanged(copyrightString); else if (!copyrightImage.isNull()) emit m_map->copyrightsChanged(copyrightImage); - connect(m_map, SIGNAL(copyrightsStyleSheetChanged(QString)), - m_copyrights.data(), SLOT(onCopyrightsStyleSheetChanged(QString))); - connect(m_copyrights.data(), SIGNAL(linkActivated(QString)), - this, SIGNAL(copyrightLinkActivated(QString))); connect(m_map, &QGeoMap::sgNodeChanged, this, &QQuickItem::update); connect(m_map, &QGeoMap::cameraCapabilitiesChanged, this, &QDeclarativeGeoMap::onCameraCapabilitiesChanged); - // set visibility of copyright notice - m_copyrights->setCopyrightsVisible(m_copyrightsVisible); - // This prefetches a buffer around the map m_map->prefetchData(); @@ -1209,7 +1135,7 @@ void QDeclarativeGeoMap::setMinimumFieldOfView(qreal minimumFieldOfView, bool us } /*! - \qmlproperty bool QtLocation::Map::minimumFieldOfView + \qmlproperty real QtLocation::Map::minimumFieldOfView This property holds the minimum valid field of view for the map, in degrees. @@ -1246,7 +1172,7 @@ void QDeclarativeGeoMap::setMaximumFieldOfView(qreal maximumFieldOfView, bool us } /*! - \qmlproperty bool QtLocation::Map::maximumFieldOfView + \qmlproperty real QtLocation::Map::maximumFieldOfView This property holds the maximum valid field of view for the map, in degrees. @@ -1264,7 +1190,7 @@ qreal QDeclarativeGeoMap::maximumFieldOfView() const } /*! - \qmlproperty bool QtLocation::Map::minimumTilt + \qmlproperty real QtLocation::Map::minimumTilt This property holds the minimum valid tilt for the map, in degrees. @@ -1301,7 +1227,7 @@ void QDeclarativeGeoMap::setMaximumTilt(qreal maximumTilt, bool userSet) } /*! - \qmlproperty bool QtLocation::Map::maximumTilt + \qmlproperty real QtLocation::Map::maximumTilt This property holds the maximum valid tilt for the map, in degrees. @@ -1469,7 +1395,7 @@ QColor QDeclarativeGeoMap::color() const } /*! - \qmlproperty color QtLocation::Map::mapReady + \qmlproperty bool QtLocation::Map::mapReady This property holds whether the map has been successfully initialized and is ready to be used. Some methods, such as \l fromCoordinate and \l toCoordinate, will not work before the map is ready. @@ -1711,6 +1637,27 @@ QGeoMap *QDeclarativeGeoMap::map() const return m_map; } +void QDeclarativeGeoMap::itemChange(ItemChange change, const ItemChangeData &value) +{ + if (change == ItemChildAddedChange) { + QQuickItem *child = value.item; + QQuickItem *mapItem = qobject_cast<QDeclarativeGeoMapItemBase *>(child); + if (!mapItem) + mapItem = qobject_cast<QDeclarativeGeoMapItemGroup *>(child); + + if (mapItem) { + qreal z = mapItem->z(); + if (z > m_maxChildZ) { // Ignore children removal + m_maxChildZ = z; + // put the copyrights notice object at the highest z order + if (m_copyrights) + m_copyrights->setCopyrightsZ(m_maxChildZ + 1); + } + } + } + QQuickItem::itemChange(change, value); +} + /*! \internal */ @@ -1743,6 +1690,32 @@ bool QDeclarativeGeoMap::isInteractive() return (m_gestureArea->enabled() && m_gestureArea->acceptedGestures()) || m_gestureArea->isActive(); } +void QDeclarativeGeoMap::attachCopyrightNotice(bool initialVisibility) +{ + if (initialVisibility) { + ++m_copyNoticesVisible; + if (m_map) + m_map->setCopyrightVisible(m_copyNoticesVisible > 0); + } +} + +void QDeclarativeGeoMap::detachCopyrightNotice(bool currentVisibility) +{ + if (currentVisibility) { + --m_copyNoticesVisible; + if (m_map) + m_map->setCopyrightVisible(m_copyNoticesVisible > 0); + } +} + +void QDeclarativeGeoMap::onAttachedCopyrightNoticeVisibilityChanged() +{ + QDeclarativeGeoMapCopyrightNotice *copy = static_cast<QDeclarativeGeoMapCopyrightNotice *>(sender()); + m_copyNoticesVisible += ( int(copy->copyrightsVisible()) * 2 - 1); + if (m_map) + m_map->setCopyrightVisible(m_copyNoticesVisible > 0); +} + /*! \internal */ @@ -2262,10 +2235,10 @@ void QDeclarativeGeoMap::fitViewportToMapItemsRefine(bool refine, bool onlyVisib if (m_mapItems.size() == 0) return; - double minX = 0; - double maxX = 0; - double minY = 0; - double maxY = 0; + double minX = qInf(); + double maxX = -qInf(); + double minY = qInf(); + double maxY = -qInf(); double topLeftX = 0; double topLeftY = 0; double bottomRightX = 0; @@ -2282,13 +2255,11 @@ void QDeclarativeGeoMap::fitViewportToMapItemsRefine(bool refine, bool onlyVisib continue; // skip quick items in the first pass and refine the fit later - if (refine) { - QDeclarativeGeoMapQuickItem *quickItem = - qobject_cast<QDeclarativeGeoMapQuickItem*>(item); - if (quickItem) { + QDeclarativeGeoMapQuickItem *quickItem = + qobject_cast<QDeclarativeGeoMapQuickItem*>(item); + if (refine && quickItem) { haveQuickItem = true; continue; - } } // Force map items to update immediately. Needed to ensure correct item size and positions // when recursively calling this function. @@ -2296,25 +2267,35 @@ void QDeclarativeGeoMap::fitViewportToMapItemsRefine(bool refine, bool onlyVisib // in relation to // a) fitViewportToMapItems // b) presence of MouseArea + // + // This is also legacy code. It must be updated to not operate on screen sizes. if (item->isPolishScheduled()) item->updatePolish(); - topLeftX = item->position().x(); - topLeftY = item->position().y(); - bottomRightX = topLeftX + item->width(); - bottomRightY = topLeftY + item->height(); + if (quickItem && quickItem->matrix_ && !quickItem->matrix_->m_matrix.isIdentity()) { + // TODO: recalculate the center/zoom level so that the item becomes projectable again + if (quickItem->zoomLevel() == 0.0) // the item is unprojectable, should be skipped. + continue; - if (itemCount == 0) { - minX = topLeftX; - maxX = bottomRightX; - minY = topLeftY; - maxY = bottomRightY; + QRectF brect = item->boundingRect(); + brect = quickItem->matrix_->m_matrix.mapRect(brect); + QPointF transformedPosition = quickItem->matrix_->m_matrix * item->position(); + topLeftX = transformedPosition.x(); + topLeftY = transformedPosition.y(); + bottomRightX = topLeftX + brect.width(); + bottomRightY = topLeftY + brect.height(); } else { - minX = qMin(minX, topLeftX); - maxX = qMax(maxX, bottomRightX); - minY = qMin(minY, topLeftY); - maxY = qMax(maxY, bottomRightY); + topLeftX = item->position().x(); + topLeftY = item->position().y(); + bottomRightX = topLeftX + item->width(); + bottomRightY = topLeftY + item->height(); } + + minX = qMin(minX, topLeftX); + maxX = qMax(maxX, bottomRightX); + minY = qMin(minY, topLeftY); + maxY = qMax(maxY, bottomRightY); + ++itemCount; } diff --git a/src/location/declarativemaps/qdeclarativegeomap_p.h b/src/location/declarativemaps/qdeclarativegeomap_p.h index ac3afd2c2d43ac64493a215a540c6a9e8ed2e761..0d5ae481c8a2ecd9998cf7d5e35193323b920ff8 100644 --- a/src/location/declarativemaps/qdeclarativegeomap_p.h +++ b/src/location/declarativemaps/qdeclarativegeomap_p.h @@ -61,6 +61,7 @@ #include <QtGui/QColor> #include <QtPositioning/qgeorectangle.h> #include <QtLocation/private/qgeomap_p.h> +#include <QtQuick/private/qquickitemchangelistener_p.h> QT_BEGIN_NAMESPACE @@ -196,6 +197,9 @@ public: QGeoServiceProvider::Error error() const; QGeoMap* map() const; + // From QQuickItem + void itemChange(ItemChange, const ItemChangeData &) override; + Q_SIGNALS: void pluginChanged(QDeclarativeGeoServiceProvider *plugin); void zoomLevelChanged(qreal zoomLevel); @@ -247,9 +251,9 @@ protected: private Q_SLOTS: void mappingManagerInitialized(); void pluginReady(); - void onMapChildrenChanged(); void onSupportedMapTypesChanged(); void onCameraCapabilitiesChanged(const QGeoCameraCapabilities &oldCameraCapabilities); + void onAttachedCopyrightNoticeVisibilityChanged(); private: void setupMapView(QDeclarativeGeoMapItemView *view); @@ -258,6 +262,8 @@ private: void fitViewportToMapItemsRefine(bool refine, bool onlyVisible); void fitViewportToGeoShape(); bool isInteractive(); + void attachCopyrightNotice(bool initialVisibility); + void detachCopyrightNotice(bool currentVisibility); private: QDeclarativeGeoServiceProvider *m_plugin; @@ -296,6 +302,10 @@ private: qreal m_userMinimumFieldOfView; qreal m_userMaximumFieldOfView; + int m_copyNoticesVisible = 0; + qreal m_maxChildZ = 0; + + friend class QDeclarativeGeoMapItem; friend class QDeclarativeGeoMapItemView; friend class QQuickGeoMapGestureArea; diff --git a/src/location/declarativemaps/qdeclarativegeomapcopyrightsnotice.cpp b/src/location/declarativemaps/qdeclarativegeomapcopyrightsnotice.cpp index 3e68ac9752c4eb95cbe810c3d778717c8497f5fa..7edfc9fff7f1c2be2a38f1a0733751a2ddd3554d 100644 --- a/src/location/declarativemaps/qdeclarativegeomapcopyrightsnotice.cpp +++ b/src/location/declarativemaps/qdeclarativegeomapcopyrightsnotice.cpp @@ -43,9 +43,17 @@ #include <QtQuick/private/qquickanchors_p.h> #include <QtQuick/private/qquickanchors_p_p.h> #include <QtLocation/private/qdeclarativegeomap_p.h> +#include <QtQuick/private/qquickpainteditem_p.h> QT_BEGIN_NAMESPACE +class QDeclarativeGeoMapCopyrightNoticePrivate: public QQuickPaintedItemPrivate +{ + Q_DECLARE_PUBLIC(QDeclarativeGeoMapCopyrightNotice) +public: + virtual void setVisible(bool visible); +}; + /*! \qmltype MapCopyrightNotice \instantiates QDeclarativeGeoMapCopyrightNotice @@ -99,6 +107,7 @@ QDeclarativeGeoMapCopyrightNotice::QDeclarativeGeoMapCopyrightNotice(QQuickItem QDeclarativeGeoMapCopyrightNotice::~QDeclarativeGeoMapCopyrightNotice() { + setMapSource(nullptr); } void QDeclarativeGeoMapCopyrightNotice::anchorToBottomLeft() @@ -112,35 +121,41 @@ void QDeclarativeGeoMapCopyrightNotice::anchorToBottomLeft() } } -void QDeclarativeGeoMapCopyrightNotice::setMapSource(QDeclarativeGeoMap *mapSource) +void QDeclarativeGeoMapCopyrightNotice::setMapSource(QDeclarativeGeoMap *map) { - if (m_mapSource == mapSource) + if (m_mapSource == map) return; if (m_mapSource) { // disconnect this object from current map source + m_mapSource->detachCopyrightNotice(copyrightsVisible()); m_mapSource->disconnect(this); m_mapSource->m_map->disconnect(this); - m_copyrightsHtml->clear(); + if (m_copyrightsHtml) + m_copyrightsHtml->clear(); m_copyrightsImage = QImage(); m_mapSource = nullptr; } - if (mapSource) { - m_mapSource = mapSource; + if (map) { + m_mapSource = map; + m_mapSource->attachCopyrightNotice(copyrightsVisible()); + connect(this, &QDeclarativeGeoMapCopyrightNotice::copyrightsVisibleChanged, + mapSource(), &QDeclarativeGeoMap::onAttachedCopyrightNoticeVisibilityChanged); + // First update the copyright. Only Image will do here, no need to store HTML right away. - if (mapSource->m_copyrights && !mapSource->m_copyrights->m_copyrightsImage.isNull()) - m_copyrightsImage = mapSource->m_copyrights->m_copyrightsImage; + if (m_mapSource->m_copyrights && !m_mapSource->m_copyrights->m_copyrightsImage.isNull()) + m_copyrightsImage = m_mapSource->m_copyrights->m_copyrightsImage; - connect(m_mapSource, SIGNAL(copyrightsChanged(QImage)), + connect(mapSource(), SIGNAL(copyrightsChanged(QImage)), this, SLOT(copyrightsChanged(QImage))); - connect(m_mapSource, SIGNAL(copyrightsChanged(QString)), + connect(mapSource(), SIGNAL(copyrightsChanged(QString)), this, SLOT(copyrightsChanged(QString))); if (m_mapSource->m_map) connectMap(); else - connect(m_mapSource, &QDeclarativeGeoMap::mapReadyChanged, this, &QDeclarativeGeoMapCopyrightNotice::connectMap); + connect(mapSource(), &QDeclarativeGeoMap::mapReadyChanged, this, &QDeclarativeGeoMapCopyrightNotice::connectMap); } } @@ -149,7 +164,7 @@ void QDeclarativeGeoMapCopyrightNotice::connectMap() connect(m_mapSource->m_map, SIGNAL(copyrightsStyleSheetChanged(QString)), this, SLOT(onCopyrightsStyleSheetChanged(QString))); connect(this, SIGNAL(linkActivated(QString)), - m_mapSource, SIGNAL(copyrightLinkActivated(QString))); + mapSource(), SIGNAL(copyrightLinkActivated(QString))); onCopyrightsStyleSheetChanged(m_mapSource->m_map->copyrightsStyleSheet()); @@ -159,7 +174,7 @@ void QDeclarativeGeoMapCopyrightNotice::connectMap() QDeclarativeGeoMap *QDeclarativeGeoMapCopyrightNotice::mapSource() { - return m_mapSource; + return m_mapSource.data(); } QString QDeclarativeGeoMapCopyrightNotice::styleSheet() const @@ -253,20 +268,36 @@ void QDeclarativeGeoMapCopyrightNotice::createCopyright() m_copyrightsHtml->setDocumentMargin(0); } +void QDeclarativeGeoMapCopyrightNoticePrivate::setVisible(bool visible) +{ + Q_Q(QDeclarativeGeoMapCopyrightNotice); + q->m_copyrightsVisible = visible; + QQuickItemPrivate::setVisible(visible); +} + /*! \internal */ void QDeclarativeGeoMapCopyrightNotice::setCopyrightsVisible(bool visible) { + Q_D(QDeclarativeGeoMapCopyrightNotice); + if (visible == m_copyrightsVisible) + return; + m_copyrightsVisible = visible; + d->QQuickItemPrivate::setVisible(!m_copyrightsImage.isNull() && visible); + emit copyrightsVisibleChanged(); +} - setVisible(!m_copyrightsImage.isNull() && visible); +bool QDeclarativeGeoMapCopyrightNotice::copyrightsVisible() const +{ + return m_copyrightsVisible; } /*! \internal */ -void QDeclarativeGeoMapCopyrightNotice::setCopyrightsZ(int copyrightsZ) +void QDeclarativeGeoMapCopyrightNotice::setCopyrightsZ(qreal copyrightsZ) { setZ(copyrightsZ); update(); @@ -277,6 +308,7 @@ void QDeclarativeGeoMapCopyrightNotice::setCopyrightsZ(int copyrightsZ) */ void QDeclarativeGeoMapCopyrightNotice::copyrightsChanged(const QImage ©rightsImage) { + Q_D(QDeclarativeGeoMapCopyrightNotice); delete m_copyrightsHtml; m_copyrightsHtml = 0; @@ -286,20 +318,19 @@ void QDeclarativeGeoMapCopyrightNotice::copyrightsChanged(const QImage ©righ setKeepMouseGrab(false); setAcceptedMouseButtons(Qt::NoButton); - setVisible(m_copyrightsVisible); + d->QQuickItemPrivate::setVisible(m_copyrightsVisible && !m_copyrightsImage.isNull()); update(); } void QDeclarativeGeoMapCopyrightNotice::copyrightsChanged(const QString ©rightsHtml) { + Q_D(QDeclarativeGeoMapCopyrightNotice); if (copyrightsHtml.isEmpty()) { - setVisible(false); + d->QQuickItemPrivate::setVisible(false); return; - } else if (!m_copyrightsVisible) { - setVisible(false); - } else { - setVisible(true); + } else { + d->QQuickItemPrivate::setVisible(m_copyrightsVisible); } // Divfy, so we can style the background. The extra <span> is a diff --git a/src/location/declarativemaps/qdeclarativegeomapcopyrightsnotice_p.h b/src/location/declarativemaps/qdeclarativegeomapcopyrightsnotice_p.h index 7189e65ccfc1fc4dbfea6927469062eeb0d6be2b..0cf06d127e37e930dd7020f8c13591ba24245b2e 100644 --- a/src/location/declarativemaps/qdeclarativegeomapcopyrightsnotice_p.h +++ b/src/location/declarativemaps/qdeclarativegeomapcopyrightsnotice_p.h @@ -52,13 +52,14 @@ #include <QtLocation/private/qlocationglobal_p.h> #include <QtGui/QImage> +#include <QPointer> #include <QtQuick/QQuickPaintedItem> QT_BEGIN_NAMESPACE class QTextDocument; class QDeclarativeGeoMap; - +class QDeclarativeGeoMapCopyrightNoticePrivate; class Q_LOCATION_PRIVATE_EXPORT QDeclarativeGeoMapCopyrightNotice : public QQuickPaintedItem { Q_OBJECT @@ -69,9 +70,10 @@ public: QDeclarativeGeoMapCopyrightNotice(QQuickItem *parent = nullptr); ~QDeclarativeGeoMapCopyrightNotice(); - void setCopyrightsZ(int copyrightsZ); + void setCopyrightsZ(qreal copyrightsZ); void setCopyrightsVisible(bool visible); + bool copyrightsVisible() const; void anchorToBottomLeft(); void setMapSource(QDeclarativeGeoMap *mapSource); @@ -90,6 +92,7 @@ signals: void mapSourceChanged(); void backgroundColorChanged(const QColor &color); void styleSheetChanged(const QString &styleSheet); + void copyrightsVisibleChanged(); protected: void paint(QPainter *painter) override; @@ -106,10 +109,13 @@ private: QImage m_copyrightsImage; QString m_activeAnchor; bool m_copyrightsVisible; - QDeclarativeGeoMap *m_mapSource; + QPointer<QDeclarativeGeoMap> m_mapSource; QColor m_backgroundColor; QString m_styleSheet; bool m_userDefinedStyleSheet; + + Q_DISABLE_COPY(QDeclarativeGeoMapCopyrightNotice) + Q_DECLARE_PRIVATE(QDeclarativeGeoMapCopyrightNotice) }; QT_END_NAMESPACE diff --git a/src/location/declarativemaps/qdeclarativegeomapitembase.cpp b/src/location/declarativemaps/qdeclarativegeomapitembase.cpp index fed0a74b6181423608df4bee491c0af104a5a96f..7b3950db9c0c46bd381b91a0d23c205cf52b7e04 100644 --- a/src/location/declarativemaps/qdeclarativegeomapitembase.cpp +++ b/src/location/declarativemaps/qdeclarativegeomapitembase.cpp @@ -222,9 +222,14 @@ bool QDeclarativeGeoMapItemBase::childMouseEventFilter(QQuickItem *item, QEvent { Q_UNUSED(item) if (event->type() == QEvent::MouseButtonPress && !contains(static_cast<QMouseEvent*>(event)->pos())) { - // This is an evil hack: in case of items that are not rectangles, we never accept the event. - // Instead the events are now delivered to QDeclarativeGeoMapItemBase which doesn't to anything with them. - // The map below it still works since it filters events and steals the events at some point. + // In case of items that are not rectangles, this filter is used to test if the event has landed + // inside the actual item shape. + // If so, the method returns true, meaning that it prevents the event delivery to child "*item" (for example, + // a mouse area that is on top of this map item). + // However, this method sets "accepted" to false, so that the event can still be passed further up, + // specifically to the parent Map, that is a sort of flickable. + // Otherwise, if the event is not contained within the map item, the method returns false, meaning the event + // is delivered to the child *item (like the mouse area associated). event->setAccepted(false); return true; } diff --git a/src/location/declarativemaps/qdeclarativegeomapquickitem_p.h b/src/location/declarativemaps/qdeclarativegeomapquickitem_p.h index 3e7573714b5e21d0a345c9f8f5fc2bbb8f955efd..8e2c27852e883f972a0f33aaa4024ac92914eb59 100644 --- a/src/location/declarativemaps/qdeclarativegeomapquickitem_p.h +++ b/src/location/declarativemaps/qdeclarativegeomapquickitem_p.h @@ -126,6 +126,8 @@ private: bool mapAndSourceItemSet_; bool updatingGeometry_; QMapQuickItemMatrix4x4 *matrix_; + + friend class QDeclarativeGeoMap; }; QT_END_NAMESPACE diff --git a/src/location/declarativemaps/qdeclarativepolygonmapitem.cpp b/src/location/declarativemaps/qdeclarativepolygonmapitem.cpp index aa3a3bd9871cd1fd46b344db9d6a1978ccb0f019..5eda137cc63d2367fa2c13872085f05f13600a43 100644 --- a/src/location/declarativemaps/qdeclarativepolygonmapitem.cpp +++ b/src/location/declarativemaps/qdeclarativepolygonmapitem.cpp @@ -304,7 +304,7 @@ void QGeoMapPolygonGeometry::updateScreenPoints(const QGeoMap &map) screenIndices_.clear(); for (const auto &p : poly) screenVertices_ << QPointF(p[0], p[1]); - std::vector<N> indices = mapbox::earcut<N>(polygon); + std::vector<N> indices = qt_mapbox::earcut<N>(polygon); for (const auto &i: indices) screenIndices_ << quint32(i); } diff --git a/src/location/maps/qgeomap.cpp b/src/location/maps/qgeomap.cpp index d21c8b1145262096b6175b312e8ce3b77e5ec3c0..af40b33bbb256a47664a2cabf68cfe4d9c31bee9 100644 --- a/src/location/maps/qgeomap.cpp +++ b/src/location/maps/qgeomap.cpp @@ -308,6 +308,15 @@ void QGeoMap::setAcceptedGestures(bool pan, bool flick, bool pinch, bool rotate, Q_UNUSED(tilt) } +void QGeoMap::setCopyrightVisible(bool visible) +{ + Q_D(QGeoMap); + if (d->m_copyrightVisible == visible) + return; + + d->m_copyrightVisible = visible; +} + QGeoMapPrivate::QGeoMapPrivate(QGeoMappingManagerEngine *engine, QGeoProjection *geoProjection) : QObjectPrivate(), m_geoProjection(geoProjection), @@ -395,4 +404,14 @@ double QGeoMapPrivate::mapHeight() const return 0; // override this for maps supporting other projections } +void QGeoMapPrivate::setCopyrightVisible(bool visible) +{ + m_copyrightVisible = visible; +} + +bool QGeoMapPrivate::copyrightVisible() const +{ + return m_copyrightVisible; +} + QT_END_NAMESPACE diff --git a/src/location/maps/qgeomap_p.h b/src/location/maps/qgeomap_p.h index 835a946aae577ad86f2df613dc42f76306575bd3..8e4a8839465c561ad2b6f3003f4a191661910848 100644 --- a/src/location/maps/qgeomap_p.h +++ b/src/location/maps/qgeomap_p.h @@ -150,6 +150,8 @@ public: virtual bool anchorCoordinateToPoint(const QGeoCoordinate &coordinate, const QPointF &anchorPoint); virtual bool fitViewportToGeoRectangle(const QGeoRectangle &rectangle); + virtual void setCopyrightVisible(bool visible); + protected: QGeoMap(QGeoMapPrivate &dd, QObject *parent = 0); void setCameraData(const QGeoCameraData &cameraData); diff --git a/src/location/maps/qgeomap_p_p.h b/src/location/maps/qgeomap_p_p.h index 615571d15a23b92092935b9ca1901bc89a0b5ed0..ff7ca318bec4d54c2d722dc4f9d019a5d8c2dbcd 100644 --- a/src/location/maps/qgeomap_p_p.h +++ b/src/location/maps/qgeomap_p_p.h @@ -100,6 +100,9 @@ protected: virtual double mapWidth() const; virtual double mapHeight() const; + virtual void setCopyrightVisible(bool visible); + virtual bool copyrightVisible() const; + protected: QSize m_viewportSize; QGeoProjection *m_geoProjection; @@ -109,6 +112,7 @@ protected: QList<QGeoMapParameter *> m_mapParameters; QList<QDeclarativeGeoMapItemBase *> m_mapItems; QGeoCameraCapabilities m_cameraCapabilities; + bool m_copyrightVisible = true; }; QT_END_NAMESPACE diff --git a/src/location/maps/qgeorouteparserosrmv5.cpp b/src/location/maps/qgeorouteparserosrmv5.cpp index df6c65ef2c9c199252b376103c4f54fd593386d0..63610485501ed6d54f87da0386cb3657c813e90a 100644 --- a/src/location/maps/qgeorouteparserosrmv5.cpp +++ b/src/location/maps/qgeorouteparserosrmv5.cpp @@ -976,7 +976,7 @@ QUrl QGeoRouteParserOsrmV5Private::requestUrl(const QGeoRouteRequest &request, c routingUrl.append(QLatin1Char(';')); bearings.append(QLatin1Char(';')); } - routingUrl.append(QString::number(c.longitude())).append(QLatin1Char(',')).append(QString::number(c.latitude())); + routingUrl.append(QString::number(c.longitude(), 'f', 7)).append(QLatin1Char(',')).append(QString::number(c.latitude(), 'f', 7)); if (metadata.size() > i) { const QVariantMap &meta = metadata.at(i); if (meta.contains(QStringLiteral("bearing"))) { diff --git a/src/location/maps/qgeotiledmap.cpp b/src/location/maps/qgeotiledmap.cpp index 81eb3b14256ade4413ab0532871b1103510b0253..f903b94ef03e9fc25edd56c326efb3e53f43b3cd 100644 --- a/src/location/maps/qgeotiledmap.cpp +++ b/src/location/maps/qgeotiledmap.cpp @@ -150,6 +150,17 @@ QGeoMap::Capabilities QGeoTiledMap::capabilities() const | SupportsAnchoringCoordinate); } +void QGeoTiledMap::setCopyrightVisible(bool visible) +{ + Q_D(QGeoTiledMap); + if (visible == d->m_copyrightVisible) + return; + + QGeoMap::setCopyrightVisible(visible); + if (visible) + evaluateCopyrights(d->m_mapScene->visibleTiles()); +} + void QGeoTiledMap::clearScene(int mapId) { Q_D(QGeoTiledMap); @@ -326,7 +337,7 @@ void QGeoTiledMapPrivate::updateScene() bool newTilesIntroduced = !m_mapScene->visibleTiles().contains(tiles); m_mapScene->setVisibleTiles(tiles); - if (newTilesIntroduced) + if (newTilesIntroduced && m_copyrightVisible) q->evaluateCopyrights(tiles); // don't request tiles that are already built and textured @@ -388,7 +399,8 @@ void QGeoTiledMapPrivate::changeViewportSize(const QSize& size) m_cache->setMinTextureUsage(newSize); } - q->evaluateCopyrights(m_visibleTiles->createTiles()); + if (m_copyrightVisible) + q->evaluateCopyrights(m_mapScene->visibleTiles()); updateScene(); } diff --git a/src/location/maps/qgeotiledmap_p.h b/src/location/maps/qgeotiledmap_p.h index ad3c9af9c331f7add98169c9de2a9fe358fd1763..a162bdbcdea94f9989cf37d61483fcec93e65eb7 100644 --- a/src/location/maps/qgeotiledmap_p.h +++ b/src/location/maps/qgeotiledmap_p.h @@ -88,6 +88,8 @@ public: void clearData() override; Capabilities capabilities() const override; + void setCopyrightVisible(bool visible) override; + public Q_SLOTS: virtual void clearScene(int mapId); diff --git a/src/plugins/position/corelocation/corelocation.pro b/src/plugins/position/corelocation/corelocation.pro index 947a1d0c8c9c45207d4eec352ce0e3de1f4c94ac..85a5aaed88663a276daa73f36decf9317ea2d9d5 100644 --- a/src/plugins/position/corelocation/corelocation.pro +++ b/src/plugins/position/corelocation/corelocation.pro @@ -1,6 +1,6 @@ TARGET = qtposition_cl -QT = core positioning +QT = core core-private positioning OBJECTIVE_SOURCES += \ qgeopositioninfosource_cl.mm \ diff --git a/src/plugins/position/corelocation/qgeopositioninfosource_cl.mm b/src/plugins/position/corelocation/qgeopositioninfosource_cl.mm index 3fac056e0a380fc65cf2f35ece77353512e53b98..94c5b80762c7ad2464a670a855024a4d6df3ef24 100644 --- a/src/plugins/position/corelocation/qgeopositioninfosource_cl.mm +++ b/src/plugins/position/corelocation/qgeopositioninfosource_cl.mm @@ -40,6 +40,7 @@ #include <QTimerEvent> #include <QDebug> #include <QtCore/qglobal.h> +#include <QtCore/private/qglobal_p.h> #include "qgeopositioninfosource_cl_p.h" @@ -141,15 +142,17 @@ bool QGeoPositionInfoSourceCL::enableLocationManager() if (!m_locationManager) { m_locationManager = [[CLLocationManager alloc] init]; -#ifdef Q_OS_IOS - NSDictionary<NSString *, id> *infoDict = [[NSBundle mainBundle] infoDictionary]; - if (id value = [infoDict objectForKey:@"UIBackgroundModes"]) { - if ([value isKindOfClass:[NSArray class]]) { - NSArray *modes = static_cast<NSArray *>(value); - for (id mode in modes) { - if ([@"location" isEqualToString:mode]) { - m_locationManager.allowsBackgroundLocationUpdates = YES; - break; +#if defined(Q_OS_IOS) || defined(Q_OS_WATCHOS) + if (__builtin_available(watchOS 4.0, *)) { + NSDictionary<NSString *, id> *infoDict = [[NSBundle mainBundle] infoDictionary]; + if (id value = [infoDict objectForKey:@"UIBackgroundModes"]) { + if ([value isKindOfClass:[NSArray class]]) { + NSArray *modes = static_cast<NSArray *>(value); + for (id mode in modes) { + if ([@"location" isEqualToString:mode]) { + m_locationManager.allowsBackgroundLocationUpdates = YES; + break; + } } } } @@ -162,10 +165,12 @@ bool QGeoPositionInfoSourceCL::enableLocationManager() // These two methods are new in iOS 8. They require NSLocationAlwaysUsageDescription // and NSLocationWhenInUseUsageDescription to be set in Info.plist to work (methods are // noop if there are no such entries in plist). - if ([m_locationManager respondsToSelector:@selector(requestAlwaysAuthorization)]) - [m_locationManager performSelector:@selector(requestAlwaysAuthorization)]; - if ([m_locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) - [m_locationManager performSelector:@selector(requestWhenInUseAuthorization)]; +#ifndef Q_OS_MACOS +#ifndef Q_OS_TVOS + [m_locationManager requestAlwaysAuthorization]; +#endif + [m_locationManager requestWhenInUseAuthorization]; +#endif } return (m_locationManager != 0); diff --git a/src/positioning/doc/src/qtpositioning.qdoc b/src/positioning/doc/src/qtpositioning.qdoc index 81de8beca9a9f159a81f4977d9e616fb517a2dce..2a966b194579cf9e4985e3fe9f6ad6e4caf94759 100644 --- a/src/positioning/doc/src/qtpositioning.qdoc +++ b/src/positioning/doc/src/qtpositioning.qdoc @@ -66,7 +66,9 @@ Currently the API is supported on \l {Qt for Android}{Android}, \l {Qt for iOS}{ \l {Qt for Linux/X11}{Linux} (using \l {http://www.freedesktop.org/wiki/Software/GeoClue}{GeoClue version 0.12.99}), \l {Qt for Windows}{Windows} (with GPS receivers exposed as a serial port providing NMEA sentences), -and \l {Qt for WinRT}{WinRT} (using Windows.Devices.Geolocation). +and \l {Qt for WinRT}{WinRT} (using Windows.Devices.Geolocation). Note that the \l {Qt for WinRT}{WinRT} +implementation can also be used in Win32 Desktop uses cases if the underlying platform is Windows 10 +or later. \section1 Overview diff --git a/src/positioning/positioning.pro b/src/positioning/positioning.pro index 9bf10ae6bac06a5bc3824fdeed094860e7c70ce2..44de23ea0aafab814d95c393dfb98941bd25dd32 100644 --- a/src/positioning/positioning.pro +++ b/src/positioning/positioning.pro @@ -59,6 +59,7 @@ PRIVATE_HEADERS += \ qdoublematrix4x4_p.h \ qgeopath_p.h \ qgeocoordinateobject_p.h \ + qgeopositioninfo_p.h \ qclipperutils_p.h SOURCES += \ diff --git a/src/positioning/qgeopositioninfo.cpp b/src/positioning/qgeopositioninfo.cpp index 84b7fa16d721b8d1ba32efa5626589c7da80e5cd..71e363d196ec0545047c1f34cf098377ef9404c7 100644 --- a/src/positioning/qgeopositioninfo.cpp +++ b/src/positioning/qgeopositioninfo.cpp @@ -37,7 +37,7 @@ ** ****************************************************************************/ #include "qgeopositioninfo.h" - +#include "qgeopositioninfo_p.h" #include <QHash> #include <QDebug> #include <QDataStream> @@ -47,14 +47,6 @@ QT_BEGIN_NAMESPACE -class QGeoPositionInfoPrivate -{ -public: - QDateTime timestamp; - QGeoCoordinate coord; - QHash<QGeoPositionInfo::Attribute, qreal> doubleAttribs; -}; - /*! \class QGeoPositionInfo \inmodule QtPositioning @@ -106,9 +98,12 @@ QGeoPositionInfo::QGeoPositionInfo(const QGeoCoordinate &coordinate, const QDate Creates a QGeoPositionInfo with the values of \a other. */ QGeoPositionInfo::QGeoPositionInfo(const QGeoPositionInfo &other) - : d(new QGeoPositionInfoPrivate) + : d(other.d->clone()) +{ +} + +QGeoPositionInfo::QGeoPositionInfo(QGeoPositionInfoPrivate &dd) : d(&dd) { - operator=(other); } /*! @@ -127,9 +122,12 @@ QGeoPositionInfo &QGeoPositionInfo::operator=(const QGeoPositionInfo & other) if (this == &other) return *this; - d->timestamp = other.d->timestamp; - d->coord = other.d->coord; - d->doubleAttribs = other.d->doubleAttribs; + delete d; + d = other.d->clone(); + +// d->timestamp = other.d->timestamp; +// d->coord = other.d->coord; +// d->doubleAttribs = other.d->doubleAttribs; return *this; } @@ -140,9 +138,7 @@ QGeoPositionInfo &QGeoPositionInfo::operator=(const QGeoPositionInfo & other) */ bool QGeoPositionInfo::operator==(const QGeoPositionInfo &other) const { - return d->timestamp == other.d->timestamp - && d->coord == other.d->coord - && d->doubleAttribs == other.d->doubleAttribs; + return *d == *other.d; } /*! @@ -356,4 +352,27 @@ QDataStream &operator>>(QDataStream &stream, QGeoPositionInfo &info) } #endif +QGeoPositionInfoPrivate::~QGeoPositionInfoPrivate() +{ + +} + +QGeoPositionInfoPrivate *QGeoPositionInfoPrivate::clone() const +{ + return new QGeoPositionInfoPrivate(*this); +} + +bool QGeoPositionInfoPrivate::operator==(const QGeoPositionInfoPrivate &other) const +{ + return timestamp == other.timestamp + && coord == other.coord + && doubleAttribs == other.doubleAttribs; +} + +QGeoPositionInfoPrivate *QGeoPositionInfoPrivate::getPimpl(const QGeoPositionInfo &info) +{ + return info.d; +} + QT_END_NAMESPACE + diff --git a/src/positioning/qgeopositioninfo.h b/src/positioning/qgeopositioninfo.h index 9f43fe8ee3d90f090479be3c1e0c67d4de50dec5..77d44970a2dfee302e0e9f81f5b84eab5b6a7f0b 100644 --- a/src/positioning/qgeopositioninfo.h +++ b/src/positioning/qgeopositioninfo.h @@ -64,6 +64,7 @@ public: QGeoPositionInfo(); QGeoPositionInfo(const QGeoCoordinate &coordinate, const QDateTime &updateTime); QGeoPositionInfo(const QGeoPositionInfo &other); + QGeoPositionInfo(QGeoPositionInfoPrivate &dd); ~QGeoPositionInfo(); QGeoPositionInfo &operator=(const QGeoPositionInfo &other); @@ -95,6 +96,7 @@ private: friend Q_POSITIONING_EXPORT QDataStream &operator>>(QDataStream &stream, QGeoPositionInfo &info); #endif QGeoPositionInfoPrivate *d; + friend class QGeoPositionInfoPrivate; }; #ifndef QT_NO_DEBUG_STREAM diff --git a/src/positioning/qgeopositioninfo_p.h b/src/positioning/qgeopositioninfo_p.h new file mode 100644 index 0000000000000000000000000000000000000000..cc4a9f3d416ae492e8c1e4caf1786437b45106b0 --- /dev/null +++ b/src/positioning/qgeopositioninfo_p.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtPositioning module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QGEOPOSITIONINFO_P_H +#define QGEOPOSITIONINFO_P_H + +#include <QtPositioning/private/qpositioningglobal_p.h> +#include "qgeopositioninfo.h" +#include <QHash> +#include <QDateTime> +#include <QtPositioning/qgeocoordinate.h> + +QT_BEGIN_NAMESPACE + +class Q_POSITIONING_PRIVATE_EXPORT QGeoPositionInfoPrivate +{ +public: + virtual ~QGeoPositionInfoPrivate(); + virtual QGeoPositionInfoPrivate *clone() const; + + virtual bool operator==(const QGeoPositionInfoPrivate &other) const; + + QDateTime timestamp; + QGeoCoordinate coord; + QHash<QGeoPositionInfo::Attribute, qreal> doubleAttribs; + + static QGeoPositionInfoPrivate *getPimpl(const QGeoPositionInfo &info); +}; + +QT_END_NAMESPACE + +#endif // QGEOPOSITIONINFO_P_H diff --git a/src/positioning/qnmeapositioninfosource.cpp b/src/positioning/qnmeapositioninfosource.cpp index 75fa96450b2743f4539739b91dbb89e3c9655cde..282f30b4447e8af364117b3a7a82f790b95bdaab 100644 --- a/src/positioning/qnmeapositioninfosource.cpp +++ b/src/positioning/qnmeapositioninfosource.cpp @@ -39,17 +39,93 @@ ** ****************************************************************************/ #include "qnmeapositioninfosource_p.h" +#include "qgeopositioninfo_p.h" #include "qlocationutils_p.h" #include <QIODevice> #include <QBasicTimer> #include <QTimerEvent> #include <QTimer> +#include <array> +#include <QDebug> #include <QtCore/QtNumeric> QT_BEGIN_NAMESPACE +#define USE_NMEA_PIMPL 0 + +#if USE_NMEA_PIMPL +class QGeoPositionInfoPrivateNmea : public QGeoPositionInfoPrivate +{ +public: + virtual ~QGeoPositionInfoPrivateNmea(); + virtual QGeoPositionInfoPrivate *clone() const; + + QList<QByteArray> nmeaSentences; +}; + + +QGeoPositionInfoPrivateNmea::~QGeoPositionInfoPrivateNmea() +{ + +} + +QGeoPositionInfoPrivate *QGeoPositionInfoPrivateNmea::clone() const +{ + return new QGeoPositionInfoPrivateNmea(*this); +} +#else +typedef QGeoPositionInfoPrivate QGeoPositionInfoPrivateNmea; +#endif + +static void mergePositions(QGeoPositionInfo &dst, const QGeoPositionInfo &src, QByteArray nmeaSentence) +{ +#if USE_NMEA_PIMPL + QGeoPositionInfoPrivateNmea *dstPimpl = static_cast<QGeoPositionInfoPrivateNmea *>(QGeoPositionInfoPrivate::getPimpl(dst)); + dstPimpl->nmeaSentences.append(nmeaSentence); +#else + Q_UNUSED(nmeaSentence) +#endif + + QGeoCoordinate c = dst.coordinate(); + if (!qIsNaN(src.coordinate().latitude())) + c.setLatitude(src.coordinate().latitude()); + if (!qIsNaN(src.coordinate().longitude())) + c.setLongitude(src.coordinate().longitude()); + if (!qIsNaN(src.coordinate().altitude())) + c.setAltitude(src.coordinate().altitude()); + dst.setCoordinate(c); + + if (!dst.timestamp().date().isValid() && src.timestamp().isValid()) // time was supposed to be set/the same already. Date can be overwritten. + dst.setTimestamp(src.timestamp()); + + static Q_DECL_CONSTEXPR std::array<QGeoPositionInfo::Attribute, 6> attrs { + { QGeoPositionInfo::GroundSpeed + ,QGeoPositionInfo::HorizontalAccuracy + ,QGeoPositionInfo::VerticalAccuracy + ,QGeoPositionInfo::Direction + ,QGeoPositionInfo::VerticalSpeed + ,QGeoPositionInfo::MagneticVariation} }; + for (const auto a: attrs) { + if (src.hasAttribute(a)) + dst.setAttribute(a, src.attribute(a)); + } + + +} + +static qint64 msecsTo(const QDateTime &from, const QDateTime &to) +{ + if (!from.time().isValid() || !to.time().isValid()) + return 0; + + if (!from.date().isValid() || !to.date().isValid()) // use only time + return from.time().msecsTo(to.time()); + + return from.msecsTo(to); +} + QNmeaRealTimeReader::QNmeaRealTimeReader(QNmeaPositionInfoSourcePrivate *sourcePrivate) : QNmeaReader(sourcePrivate) { @@ -107,25 +183,111 @@ void QNmeaSimulatedReader::readAvailableData() } } -bool QNmeaSimulatedReader::setFirstDateTime() +static int processSentence(QGeoPositionInfo &info, + QByteArray &m_nextLine, + QNmeaPositionInfoSourcePrivate *m_proxy, + QQueue<QPendingGeoPositionInfo> &m_pendingUpdates, + bool &hasFix) { - // find the first update with valid date and time - QGeoPositionInfo update; - bool hasFix = false; - while (m_proxy->m_device->bytesAvailable() > 0) { - char buf[1024]; - qint64 size = m_proxy->m_device->readLine(buf, sizeof(buf)); + int timeToNextUpdate = -1; + QDateTime prevTs; + if (m_pendingUpdates.size() > 0) + prevTs = m_pendingUpdates.head().info.timestamp(); + + // find the next update with a valid time (as long as the time is valid, + // we can calculate when the update should be emitted) + while (m_nextLine.size() || (m_proxy->m_device && m_proxy->m_device->bytesAvailable() > 0)) { + char static_buf[1024]; + char *buf = static_buf; + QByteArray nextLine; + qint64 size = 0; + if (m_nextLine.size()) { + // Read something in the previous call, but TS was later. + size = m_nextLine.size(); + nextLine = m_nextLine; + m_nextLine.clear(); + buf = nextLine.data(); + } else { + size = m_proxy->m_device->readLine(buf, sizeof(static_buf)); + } + if (size <= 0) continue; - bool ok = m_proxy->parsePosInfoFromNmeaData(buf, size, &update, &hasFix); - if (ok && update.timestamp().isValid()) { - QPendingGeoPositionInfo pending; - pending.info = update; - pending.hasFix = hasFix; - m_pendingUpdates.enqueue(pending); - return true; + + const QTime infoTime = info.timestamp().time(); // if info has been set, time must be valid. + const QDate infoDate = info.timestamp().date(); // this one might not be valid, as some sentences do not contain it + + /* + Packets containing time information are GGA, RMC, ZDA, GLL: + + GGA : GPS fix data - only time + GLL : geographic latitude and longitude - only time + RMC : recommended minimum FPOS/transit data - date/time + ZDA : only timestamp - date/time + + QLocationUtils is currently also capable of parsing VTG and GSA sentences: + + VTG: containing Track made good and ground speed + GSA: overall satellite data + + Since these sentences contain no timestamp, their content will be merged with the content + from any prior sentence that had timestamp info, if any is available. + */ + + QGeoPositionInfoPrivateNmea *pimpl = new QGeoPositionInfoPrivateNmea; + QGeoPositionInfo pos(*pimpl); + if (m_proxy->parsePosInfoFromNmeaData(buf, size, &pos, &hasFix)) { + // Date may or may not be valid, as some packets do not have date. + // If date isn't valid, match is performed on time only. + // Hence, make sure that packet blocks are generated with + // the sentences containing the full timestamp (e.g., GPRMC) *first* ! + if (infoTime.isValid()) { + if (pos.timestamp().time().isValid()) { + if (infoTime != pos.timestamp().time() || infoDate != pos.timestamp().date()) { + // Effectively read data for different update, so copy buf into m_nextLine + m_nextLine = QByteArray(buf, size); + break; + } else { + // timestamps match -- merge into info + mergePositions(info, pos, QByteArray(buf, size)); + } + } else { + // no timestamp available -- merge into info + mergePositions(info, pos, QByteArray(buf, size)); + } + } else { + // there was no info with valid TS. Overwrite with whatever is parsed. +#if USE_NMEA_PIMPL + pimpl->nmeaSentences.append(QByteArray(buf, size)); +#endif + info = pos; + } + + if (prevTs.time().isValid()) { + timeToNextUpdate = msecsTo(prevTs, info.timestamp()); + if (timeToNextUpdate < 0) // Somehow parsing expired packets, reset info + info = QGeoPositionInfo(*new QGeoPositionInfoPrivateNmea); + } } } + + return timeToNextUpdate; +} + +bool QNmeaSimulatedReader::setFirstDateTime() +{ + // find the first update with valid date and time + QGeoPositionInfo info(*new QGeoPositionInfoPrivateNmea); + bool hasFix = false; + processSentence(info, m_nextLine, m_proxy, m_pendingUpdates, hasFix); + + if (info.timestamp().time().isValid()) { // NMEA may have sentences with only time and no date. These would generate invalid positions + QPendingGeoPositionInfo pending; + pending.info = info; + pending.hasFix = hasFix; + m_pendingUpdates.enqueue(pending); + return true; + } return false; } @@ -149,37 +311,10 @@ void QNmeaSimulatedReader::timerEvent(QTimerEvent *event) void QNmeaSimulatedReader::processNextSentence() { - QGeoPositionInfo info; + QGeoPositionInfo info(*new QGeoPositionInfoPrivateNmea); bool hasFix = false; - int timeToNextUpdate = -1; - QTime prevTime; - if (m_pendingUpdates.size() > 0) - prevTime = m_pendingUpdates.head().info.timestamp().time(); - - // find the next update with a valid time (as long as the time is valid, - // we can calculate when the update should be emitted) - while (m_proxy->m_device && m_proxy->m_device->bytesAvailable() > 0) { - char buf[1024]; - qint64 size = m_proxy->m_device->readLine(buf, sizeof(buf)); - if (size <= 0) - continue; - if (m_proxy->parsePosInfoFromNmeaData(buf, size, &info, &hasFix)) { - QTime time = info.timestamp().time(); - if (time.isValid()) { - if (!prevTime.isValid()) { - timeToNextUpdate = 0; - break; - } - timeToNextUpdate = prevTime.msecsTo(time); - if (timeToNextUpdate >= 0) - break; - } else { - timeToNextUpdate = 0; - break; - } - } - } + int timeToNextUpdate = processSentence(info, m_nextLine, m_proxy, m_pendingUpdates, hasFix); if (timeToNextUpdate < 0) return; diff --git a/src/positioning/qnmeapositioninfosource_p.h b/src/positioning/qnmeapositioninfosource_p.h index 056de90d22391683d548ad9a5104a52748d84b3e..6efb5648fb04a038094826942dbbb9d33aa7fe6e 100644 --- a/src/positioning/qnmeapositioninfosource_p.h +++ b/src/positioning/qnmeapositioninfosource_p.h @@ -168,6 +168,7 @@ private: void processNextSentence(); QQueue<QPendingGeoPositionInfo> m_pendingUpdates; + QByteArray m_nextLine; int m_currTimerId; bool m_hasValidDateTime; }; diff --git a/src/positioningquick/qdeclarativepositionsource.cpp b/src/positioningquick/qdeclarativepositionsource.cpp index cfb1df290f898ba1cefb9fc2f0b18490c780b86d..d290de25fe9ace6b2ca7e2e66673f7f366f0d4bc 100644 --- a/src/positioningquick/qdeclarativepositionsource.cpp +++ b/src/positioningquick/qdeclarativepositionsource.cpp @@ -297,6 +297,7 @@ void QDeclarativePositionSource::setNmeaSource(const QUrl &nmeaSource) qDebug() << "QDeclarativePositionSource NMEA File was found: " << localFileName; #endif m_positionSource = new QNmeaPositionInfoSource(QNmeaPositionInfoSource::SimulationMode); + (qobject_cast<QNmeaPositionInfoSource *>(m_positionSource))->setUserEquivalentRangeError(2.5); // it is internally multiplied by 2 in qlocationutils_readGga (qobject_cast<QNmeaPositionInfoSource *>(m_positionSource))->setDevice(m_nmeaFile); connect(m_positionSource, SIGNAL(positionUpdated(QGeoPositionInfo)), this, SLOT(positionUpdateReceived(QGeoPositionInfo))); diff --git a/tests/auto/qnmeapositioninfosource/tst_qnmeapositioninfosource.cpp b/tests/auto/qnmeapositioninfosource/tst_qnmeapositioninfosource.cpp index 8305dc1da96d6bee664a8bb89c7f0c5c07c963d9..c77e465d6f84d54f18381bc34866185c21f8a49a 100644 --- a/tests/auto/qnmeapositioninfosource/tst_qnmeapositioninfosource.cpp +++ b/tests/auto/qnmeapositioninfosource/tst_qnmeapositioninfosource.cpp @@ -212,7 +212,7 @@ void tst_QNmeaPositionInfoSource::beginWithBufferedData_data() QTest::newRow("requestUpdate(), 1 update in buffer") << dateTimes << RequestUpdatesMethod; for (int i=1; i<3; i++) - dateTimes << dateTimes[0].addDays(i); + dateTimes << dateTimes[0].addMSecs(i * 100); QTest::newRow("startUpdates(), multiple updates in buffer") << dateTimes << StartUpdatesMethod; QTest::newRow("requestUpdate(), multiple updates in buffer") << dateTimes << RequestUpdatesMethod; } @@ -356,8 +356,10 @@ void tst_QNmeaPositionInfoSource::startUpdates_waitForValidDateTime() QNmeaPositionInfoSourceProxy *proxy = static_cast<QNmeaPositionInfoSourceProxy*>(factory.createProxy(&source)); QSignalSpy spy(proxy->source(), SIGNAL(positionUpdated(QGeoPositionInfo))); + QObject::connect(proxy->source(), &QNmeaPositionInfoSource::positionUpdated, [](const QGeoPositionInfo &info) { + qDebug() << info.timestamp(); + }); proxy->source()->startUpdates(); - proxy->feedBytes(bytes); QTRY_COMPARE(spy.count(), dateTimes.count()); @@ -373,7 +375,7 @@ void tst_QNmeaPositionInfoSource::startUpdates_waitForValidDateTime() if (pInfo.hasAttribute(QGeoPositionInfo::HorizontalAccuracy)) QVERIFY(qFuzzyCompare(pInfo.attribute(QGeoPositionInfo::HorizontalAccuracy), 35.7)); - // Generate GSA sentences have hard coded VDOP of 4.0, which corrisponds to a vertical + // Generated GSA sentences have hard coded VDOP of 4.0, which corrisponds to a vertical // accuracy of 40.8, for the user equivalent range error of 5.1 set above. QCOMPARE(pInfo.hasAttribute(QGeoPositionInfo::VerticalAccuracy), expectVerticalAccuracy[i]); @@ -393,45 +395,54 @@ void tst_QNmeaPositionInfoSource::startUpdates_waitForValidDateTime_data() QByteArray bytes; // should only receive RMC sentence and the GGA sentence *after* it - bytes += QLocationTestUtils::createGgaSentence(dt.addSecs(1).time()).toLatin1(); - bytes += QLocationTestUtils::createRmcSentence(dt.addSecs(2)).toLatin1(); - bytes += QLocationTestUtils::createGgaSentence(dt.addSecs(3).time()).toLatin1(); + bytes += QLocationTestUtils::createGgaSentence(dt.addMSecs(100).time()).toLatin1(); + bytes += QLocationTestUtils::createRmcSentence(dt.addMSecs(200)).toLatin1(); + bytes += QLocationTestUtils::createGgaSentence(dt.addMSecs(300).time()).toLatin1(); QTest::newRow("Feed GGA,RMC,GGA; expect RMC, second GGA only") - << bytes << (QList<QDateTime>() << dt.addSecs(2) << dt.addSecs(3)) - << (QList<bool>() << true << true) + << bytes << (QList<QDateTime>() << dt.addMSecs(200) << dt.addMSecs(300)) + << (QList<bool>() << true << true) // accuracies are currently cached and injected in QGeoPositionInfos that do not have it << (QList<bool>() << false << false); // should not receive ZDA (has no coordinates) but should get the GGA // sentence after it since it got the date/time from ZDA bytes.clear(); - bytes += QLocationTestUtils::createGgaSentence(dt.addSecs(1).time()).toLatin1(); - bytes += QLocationTestUtils::createZdaSentence(dt.addSecs(2)).toLatin1(); - bytes += QLocationTestUtils::createGgaSentence(dt.addSecs(3).time()).toLatin1(); + bytes += QLocationTestUtils::createGgaSentence(dt.addMSecs(100).time()).toLatin1(); + bytes += QLocationTestUtils::createZdaSentence(dt.addMSecs(200)).toLatin1(); + bytes += QLocationTestUtils::createGgaSentence(dt.addMSecs(300).time()).toLatin1(); QTest::newRow("Feed GGA,ZDA,GGA; expect second GGA only") - << bytes << (QList<QDateTime>() << dt.addSecs(3)) + << bytes << (QList<QDateTime>() << dt.addMSecs(300)) << (QList<bool>() << true) << (QList<bool>() << false); // Feed ZDA,GGA,GSA,GGA; expect vertical accuracy from second GGA. bytes.clear(); - bytes += QLocationTestUtils::createZdaSentence(dt.addSecs(1)).toLatin1(); - bytes += QLocationTestUtils::createGgaSentence(dt.addSecs(2).time()).toLatin1(); + bytes += QLocationTestUtils::createZdaSentence(dt.addMSecs(100)).toLatin1(); + bytes += QLocationTestUtils::createGgaSentence(dt.addMSecs(200).time()).toLatin1(); bytes += QLocationTestUtils::createGsaSentence().toLatin1(); - bytes += QLocationTestUtils::createGgaSentence(dt.addSecs(3).time()).toLatin1(); - QTest::newRow("Feed ZDA,GGA,GSA,GGA; expect vertical accuracy from second GGA") - << bytes << (QList<QDateTime>() << dt.addSecs(2) << dt.addSecs(3)) - << (QList<bool>() << true << true) - << (QList<bool>() << false << true); + bytes += QLocationTestUtils::createGgaSentence(dt.addMSecs(300).time()).toLatin1(); + if (m_mode == QNmeaPositionInfoSource::SimulationMode) { + QTest::newRow("Feed ZDA,GGA,GSA,GGA; expect vertical accuracy from second GGA") + << bytes << (QList<QDateTime>() << dt.addMSecs(200) << dt.addMSecs(300)) + << (QList<bool>() << true << true) + << (QList<bool>() << true << true); // First GGA gets VDOP from GSA bundled into previous, as it has no timestamp, second GGA gets the cached value. + } else { + // FixMe: remove else block once NMEA realtime mode supports timestamp-based combination of nmea sentences + QTest::newRow("Feed ZDA,GGA,GSA,GGA; expect vertical accuracy from second GGA") + << bytes << (QList<QDateTime>() << dt.addMSecs(200) << dt.addMSecs(300)) + << (QList<bool>() << true << true) + << (QList<bool>() << false << true); + + } if (m_mode == QNmeaPositionInfoSource::SimulationMode) { // In sim m_mode, should ignore sentence with a date/time before the known date/time // (in real time m_mode, everything is passed on regardless) bytes.clear(); - bytes += QLocationTestUtils::createRmcSentence(dt.addSecs(1)).toLatin1(); - bytes += QLocationTestUtils::createRmcSentence(dt.addSecs(-2)).toLatin1(); - bytes += QLocationTestUtils::createRmcSentence(dt.addSecs(2)).toLatin1(); + bytes += QLocationTestUtils::createRmcSentence(dt.addMSecs(100)).toLatin1(); + bytes += QLocationTestUtils::createRmcSentence(dt.addMSecs(-200)).toLatin1(); + bytes += QLocationTestUtils::createRmcSentence(dt.addMSecs(200)).toLatin1(); QTest::newRow("Feed good RMC, RMC with bad date/time, good RMC; expect first and third RMC only") - << bytes << (QList<QDateTime>() << dt.addSecs(1) << dt.addSecs(2)) + << bytes << (QList<QDateTime>() << dt.addMSecs(100) << dt.addMSecs(200)) << (QList<bool>() << false << false) << (QList<bool>() << false << false); }