diff options
Diffstat (limited to 'src/location/declarativemaps')
50 files changed, 16592 insertions, 0 deletions
diff --git a/src/location/declarativemaps/declarativemaps.pri b/src/location/declarativemaps/declarativemaps.pri new file mode 100644 index 00000000..1da5c7b8 --- /dev/null +++ b/src/location/declarativemaps/declarativemaps.pri @@ -0,0 +1,65 @@ +QT += quick-private network positioning-private qml-private core-private gui-private + +INCLUDEPATH += declarativemaps + +PUBLIC_HEADERS += \ + declarativemaps/error_messages.h + +PRIVATE_HEADERS += \ + declarativemaps/qdeclarativegeomapitemview_p.h \ + declarativemaps/qdeclarativegeomapitemview_p_p.h \ + declarativemaps/qdeclarativegeoserviceprovider_p.h \ + declarativemaps/qdeclarativegeocodemodel_p.h \ + declarativemaps/qdeclarativegeoroutemodel_p.h \ + declarativemaps/qdeclarativegeoroute_p.h \ + declarativemaps/qdeclarativegeoroutesegment_p.h \ + declarativemaps/qdeclarativegeomaneuver_p.h \ + declarativemaps/qdeclarativegeomap_p.h \ + declarativemaps/qdeclarativegeomaptype_p.h \ + declarativemaps/qdeclarativegeomapitembase_p.h \ + declarativemaps/qdeclarativegeomapquickitem_p.h \ + declarativemaps/qdeclarativecirclemapitem_p.h \ + declarativemaps/qdeclarativerectanglemapitem_p.h \ + declarativemaps/qdeclarativepolygonmapitem_p.h \ + declarativemaps/qdeclarativepolylinemapitem_p.h \ + declarativemaps/qdeclarativeroutemapitem_p.h \ + declarativemaps/qdeclarativegeomapparameter_p.h \ + declarativemaps/qgeomapitemgeometry_p.h \ + declarativemaps/qdeclarativegeomapcopyrightsnotice_p.h \ + declarativemaps/locationvaluetypehelper_p.h \ + declarativemaps/qquickgeomapgesturearea_p.h \ + declarativemaps/qdeclarativegeomapitemgroup_p.h \ + declarativemaps/mapitemviewdelegateincubator_p.h \ + ../imports/positioning/qquickgeocoordinateanimation_p.h + +SOURCES += \ + declarativemaps/qdeclarativegeomapitemview.cpp \ + declarativemaps/qdeclarativegeoserviceprovider.cpp \ + declarativemaps/qdeclarativegeocodemodel.cpp \ + declarativemaps/qdeclarativegeoroutemodel.cpp \ + declarativemaps/qdeclarativegeoroute.cpp \ + declarativemaps/qdeclarativegeoroutesegment.cpp \ + declarativemaps/qdeclarativegeomaneuver.cpp \ + declarativemaps/qdeclarativegeomap.cpp \ + declarativemaps/qdeclarativegeomaptype.cpp \ + declarativemaps/qdeclarativegeomapitembase.cpp \ + declarativemaps/qdeclarativegeomapquickitem.cpp \ + declarativemaps/qdeclarativecirclemapitem.cpp \ + declarativemaps/qdeclarativerectanglemapitem.cpp \ + declarativemaps/qdeclarativepolygonmapitem.cpp \ + declarativemaps/qdeclarativepolylinemapitem.cpp \ + declarativemaps/qdeclarativeroutemapitem.cpp \ + declarativemaps/qdeclarativegeomapparameter.cpp \ + declarativemaps/qgeomapitemgeometry.cpp \ + declarativemaps/qdeclarativegeomapcopyrightsnotice.cpp \ + declarativemaps/error_messages.cpp \ + declarativemaps/locationvaluetypehelper.cpp \ + declarativemaps/qquickgeomapgesturearea.cpp \ + declarativemaps/qdeclarativegeomapitemgroup.cpp \ + ../imports/positioning/qquickgeocoordinateanimation.cpp \ + declarativemaps/mapitemviewdelegateincubator.cpp + +load(qt_build_paths) +LIBS_PRIVATE += -L$$MODULE_BASE_OUTDIR/lib -lpoly2tri$$qtPlatformTargetSuffix() -lclip2tri$$qtPlatformTargetSuffix() + + diff --git a/src/location/declarativemaps/error_messages.cpp b/src/location/declarativemaps/error_messages.cpp new file mode 100644 index 00000000..a2557f79 --- /dev/null +++ b/src/location/declarativemaps/error_messages.cpp @@ -0,0 +1,52 @@ +/*************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "error_messages.h" + +QT_BEGIN_NAMESPACE + +const char CONTEXT_NAME[] = "QtLocationQML"; + +//to-be-translated error string + +const char PLUGIN_PROPERTY_NOT_SET[] = QT_TRANSLATE_NOOP("QtLocationQML", "Plugin property is not set."); +const char PLUGIN_ERROR[] = QT_TRANSLATE_NOOP("QtLocationQML", "Plugin Error (%1): %2"); +const char PLUGIN_PROVIDER_ERROR[] = QT_TRANSLATE_NOOP("QtLocationQML", "Plugin Error (%1): Could not instantiate provider"); +const char PLUGIN_NOT_VALID[] = QT_TRANSLATE_NOOP("QtLocationQML", "Plugin is not valid"); +const char CATEGORIES_NOT_INITIALIZED[] = QT_TRANSLATE_NOOP("QtLocationQML", "Unable to initialize categories"); +const char UNABLE_TO_MAKE_REQUEST[] = QT_TRANSLATE_NOOP("QtLocationQML", "Unable to create request"); +const char INDEX_OUT_OF_RANGE[] = QT_TRANSLATE_NOOP("QtLocationQML", "Index '%1' out of range"); + +QT_END_NAMESPACE diff --git a/src/location/declarativemaps/error_messages.h b/src/location/declarativemaps/error_messages.h new file mode 100644 index 00000000..81c43b34 --- /dev/null +++ b/src/location/declarativemaps/error_messages.h @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef ERROR_MESSAGES_H +#define ERROR_MESSAGES_H + +#include <QtCore/qglobal.h> + +QT_BEGIN_NAMESPACE + +extern const char CONTEXT_NAME[]; +extern const char PLUGIN_PROPERTY_NOT_SET[]; +extern const char PLUGIN_ERROR[]; +extern const char PLUGIN_PROVIDER_ERROR[]; +extern const char PLUGIN_NOT_VALID[]; +extern const char CATEGORIES_NOT_INITIALIZED[]; +extern const char UNABLE_TO_MAKE_REQUEST[]; +extern const char INDEX_OUT_OF_RANGE[]; + +QT_END_NAMESPACE + +#endif // ERROR_MESSAGES_H diff --git a/src/location/declarativemaps/locationvaluetypehelper.cpp b/src/location/declarativemaps/locationvaluetypehelper.cpp new file mode 100644 index 00000000..4f39e0b4 --- /dev/null +++ b/src/location/declarativemaps/locationvaluetypehelper.cpp @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "locationvaluetypehelper_p.h" + + +QGeoCoordinate parseCoordinate(const QJSValue &value, bool *ok) +{ + QGeoCoordinate c; + + if (value.isObject()) { + if (value.hasProperty(QStringLiteral("latitude"))) + c.setLatitude(value.property(QStringLiteral("latitude")).toNumber()); + if (value.hasProperty(QStringLiteral("longitude"))) + c.setLongitude(value.property(QStringLiteral("longitude")).toNumber()); + if (value.hasProperty(QStringLiteral("altitude"))) + c.setAltitude(value.property(QStringLiteral("altitude")).toNumber()); + + if (ok) + *ok = true; + } + + return c; +} + +QGeoRectangle parseRectangle(const QJSValue &value, bool *ok) +{ + QGeoRectangle r; + + *ok = false; + + if (value.isObject()) { + if (value.hasProperty(QStringLiteral("bottomLeft"))) { + QGeoCoordinate c = parseCoordinate(value.property(QStringLiteral("bottomLeft")), ok); + if (*ok) + r.setBottomLeft(c); + } + if (value.hasProperty(QStringLiteral("bottomRight"))) { + QGeoCoordinate c = parseCoordinate(value.property(QStringLiteral("bottomRight")), ok); + if (*ok) + r.setBottomRight(c); + } + if (value.hasProperty(QStringLiteral("topLeft"))) { + QGeoCoordinate c = parseCoordinate(value.property(QStringLiteral("topLeft")), ok); + if (*ok) + r.setTopLeft(c); + } + if (value.hasProperty(QStringLiteral("topRight"))) { + QGeoCoordinate c = parseCoordinate(value.property(QStringLiteral("topRight")), ok); + if (*ok) + r.setTopRight(c); + } + if (value.hasProperty(QStringLiteral("center"))) { + QGeoCoordinate c = parseCoordinate(value.property(QStringLiteral("center")), ok); + if (*ok) + r.setCenter(c); + } + if (value.hasProperty(QStringLiteral("height"))) + r.setHeight(value.property(QStringLiteral("height")).toNumber()); + if (value.hasProperty(QStringLiteral("width"))) + r.setWidth(value.property(QStringLiteral("width")).toNumber()); + } + + return r; +} + +QGeoCircle parseCircle(const QJSValue &value, bool *ok) +{ + QGeoCircle c; + + *ok = false; + + if (value.isObject()) { + if (value.hasProperty(QStringLiteral("center"))) { + QGeoCoordinate coord = parseCoordinate(value.property(QStringLiteral("center")), ok); + if (*ok) + c.setCenter(coord); + } + if (value.hasProperty(QStringLiteral("radius"))) + c.setRadius(value.property(QStringLiteral("radius")).toNumber()); + } + + return c; +} diff --git a/src/location/declarativemaps/locationvaluetypehelper_p.h b/src/location/declarativemaps/locationvaluetypehelper_p.h new file mode 100644 index 00000000..50038e88 --- /dev/null +++ b/src/location/declarativemaps/locationvaluetypehelper_p.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef LOCATION_VALUE_TYPE_HELPER +#define LOCATION_VALUE_TYPE_HELPER + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QJSValue> +#include <QGeoCoordinate> +#include <QGeoRectangle> +#include <QGeoCircle> + +QGeoCoordinate parseCoordinate(const QJSValue &value, bool *ok); +QGeoRectangle parseRectangle(const QJSValue &value, bool *ok); +QGeoCircle parseCircle(const QJSValue &value, bool *ok); + +#endif diff --git a/src/location/declarativemaps/mapitemviewdelegateincubator.cpp b/src/location/declarativemaps/mapitemviewdelegateincubator.cpp new file mode 100644 index 00000000..c8500e4b --- /dev/null +++ b/src/location/declarativemaps/mapitemviewdelegateincubator.cpp @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2015 Jolla Ltd, author: Aaron McCarthy <aaron.mccarthy@jollamobile.com> +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "mapitemviewdelegateincubator_p.h" +#include "qdeclarativegeomapitemview_p.h" +#include "qdeclarativegeomapitemview_p_p.h" + +QT_BEGIN_NAMESPACE + +MapItemViewDelegateIncubator::MapItemViewDelegateIncubator(QDeclarativeGeoMapItemView *view, QDeclarativeGeoMapItemViewItemData *itemData, bool batched) +: m_view(view), m_itemData(itemData), m_batched(batched) +{ +} + +void MapItemViewDelegateIncubator::statusChanged(QQmlIncubator::Status status) +{ + m_view->incubatorStatusChanged(this, status, m_batched); +} + +QT_END_NAMESPACE diff --git a/src/location/declarativemaps/mapitemviewdelegateincubator_p.h b/src/location/declarativemaps/mapitemviewdelegateincubator_p.h new file mode 100644 index 00000000..f03ae405 --- /dev/null +++ b/src/location/declarativemaps/mapitemviewdelegateincubator_p.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2015 Jolla Ltd, author: Aaron McCarthy <aaron.mccarthy@jollamobile.com> +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef MAPITEMVIEWDELEGATEINCUBATOR_H +#define MAPITEMVIEWDELEGATEINCUBATOR_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtLocation/private/qlocationglobal_p.h> +#include <QtQml/QQmlIncubator> + +QT_BEGIN_NAMESPACE + +class QDeclarativeGeoMapItemView; +class QDeclarativeGeoMapItemViewItemData; + +class Q_LOCATION_PRIVATE_EXPORT MapItemViewDelegateIncubator : public QQmlIncubator +{ +public: + MapItemViewDelegateIncubator(QDeclarativeGeoMapItemView *view, QDeclarativeGeoMapItemViewItemData *itemData, bool batched = true); + +protected: + void statusChanged(Status status) Q_DECL_OVERRIDE; + +private: + QDeclarativeGeoMapItemView *m_view; + QDeclarativeGeoMapItemViewItemData *m_itemData; + bool m_batched; + + friend class QDeclarativeGeoMapItemView; +}; + +QT_END_NAMESPACE + +#endif // MAPITEMVIEWDELEGATEINCUBATOR_H diff --git a/src/location/declarativemaps/qdeclarativecirclemapitem.cpp b/src/location/declarativemaps/qdeclarativecirclemapitem.cpp new file mode 100644 index 00000000..274225c0 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativecirclemapitem.cpp @@ -0,0 +1,689 @@ +/*************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativecirclemapitem_p.h" +#include "qdeclarativepolygonmapitem_p.h" + +#include "qwebmercator_p.h" +#include <QtLocation/private/qgeomap_p.h> + +#include <qmath.h> +#include <algorithm> + +#include <QtCore/QScopedValueRollback> +#include <QPen> +#include <QPainter> +#include <QtGui/private/qtriangulator_p.h> + +#include "qdoublevector2d_p.h" +#include "qlocationutils_p.h" +#include "qgeocircle.h" + +/* poly2tri triangulator includes */ +#include <common/shapes.h> +#include <sweep/cdt.h> + +#include <QtPositioning/private/qclipperutils_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype MapCircle + \instantiates QDeclarativeCircleMapItem + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-maps + \since Qt Location 5.5 + + \brief The MapCircle type displays a geographic circle on a Map. + + The MapCircle type displays a geographic circle on a Map, which + consists of all points that are within a set distance from one + central point. Depending on map projection, a geographic circle + may not always be a perfect circle on the screen: for instance, in + the Mercator projection, circles become ovoid in shape as they near + the poles. To display a perfect screen circle around a point, use a + MapQuickItem containing a relevant Qt Quick type instead. + + By default, the circle is displayed as a 1 pixel black border with + no fill. To change its appearance, use the color, border.color + and border.width properties. + + Internally, a MapCircle is implemented as a many-sided polygon. To + calculate the radius points it uses a spherical model of the Earth, + similar to the atDistanceAndAzimuth method of the \l {coordinate} + type. These two things can occasionally have implications for the + accuracy of the circle's shape, depending on position and map + projection. + + \note Dragging a MapCircle (through the use of \l MouseArea) + causes new points to be generated at the same distance (in meters) + from the center. This is in contrast to other map items which store + their dimensions in terms of latitude and longitude differences between + vertices. + + \section2 Performance + + MapCircle performance is almost equivalent to that of a MapPolygon with + the same number of vertices. There is a small amount of additional + overhead with respect to calculating the vertices first. + + Like the other map objects, MapCircle is normally drawn without a smooth + appearance. Setting the opacity property will force the object to be + blended, which decreases performance considerably depending on the graphics + hardware in use. + + \section2 Example Usage + + The following snippet shows a map containing a MapCircle, centered at + the coordinate (-27, 153) with a radius of 5km. The circle is + filled in green, with a 3 pixel black border. + + \code + Map { + MapCircle { + center { + latitude: -27.5 + longitude: 153.0 + } + radius: 5000.0 + color: 'green' + border.width: 3 + } + } + \endcode + + \image api-mapcircle.png +*/ + +static const int CircleSamples = 128; + +struct Vertex +{ + QVector2D position; +}; + +QGeoMapCircleGeometry::QGeoMapCircleGeometry() +{ +} + +/*! + \internal +*/ +void QGeoMapCircleGeometry::updateScreenPointsInvert(const QList<QDoubleVector2D> &circlePath, const QGeoMap &map) +{ + // Not checking for !screenDirty anymore, as everything is now recalculated. + clear(); + if (map.viewportWidth() == 0 || map.viewportHeight() == 0 || circlePath.size() < 3) // a circle requires at least 3 points; + return; + + /* + * No special case for no tilting as these items are very rare, and usually at most one per map. + * + * Approach: + * 1) subtract the circle from a rectangle filling the whole map, *in wrapped mercator space* + * 2) clip the resulting geometries against the visible region, *in wrapped mercator space* + * 3) create a QPainterPath with each of the resulting polygons projected to screen + * 4) use qTriangulate() to triangulate the painter path + */ + + // 1) + double topLati = QLocationUtils::mercatorMaxLatitude(); + double bottomLati = -(QLocationUtils::mercatorMaxLatitude()); + double leftLongi = QLocationUtils::mapLeftLongitude(map.cameraData().center().longitude()); + double rightLongi = QLocationUtils::mapRightLongitude(map.cameraData().center().longitude()); + + srcOrigin_ = QGeoCoordinate(topLati,leftLongi); + QDoubleVector2D tl = map.geoProjection().geoToWrappedMapProjection(QGeoCoordinate(topLati,leftLongi)); + QDoubleVector2D tr = map.geoProjection().geoToWrappedMapProjection(QGeoCoordinate(topLati,rightLongi)); + QDoubleVector2D br = map.geoProjection().geoToWrappedMapProjection(QGeoCoordinate(bottomLati,rightLongi)); + QDoubleVector2D bl = map.geoProjection().geoToWrappedMapProjection(QGeoCoordinate(bottomLati,leftLongi)); + + QList<QDoubleVector2D> fill; + fill << tl << tr << br << bl; + + QList<QDoubleVector2D> hole; + for (const QDoubleVector2D &c: circlePath) + hole << map.geoProjection().wrapMapProjection(c); + + c2t::clip2tri clipper; + clipper.addSubjectPath(QClipperUtils::qListToPath(fill), true); + clipper.addClipPolygon(QClipperUtils::qListToPath(hole)); + Paths difference = clipper.execute(c2t::clip2tri::Difference, QtClipperLib::pftEvenOdd, QtClipperLib::pftEvenOdd); + + // 2) + QDoubleVector2D lb = map.geoProjection().geoToWrappedMapProjection(srcOrigin_); + QList<QList<QDoubleVector2D> > clippedPaths; + const QList<QDoubleVector2D> &visibleRegion = map.geoProjection().visibleRegion(); + if (visibleRegion.size()) { + clipper.clearClipper(); + for (const Path &p: difference) + clipper.addSubjectPath(p, true); + clipper.addClipPolygon(QClipperUtils::qListToPath(visibleRegion)); + Paths res = clipper.execute(c2t::clip2tri::Intersection, QtClipperLib::pftEvenOdd, QtClipperLib::pftEvenOdd); + clippedPaths = QClipperUtils::pathsToQList(res); + + // 2.1) update srcOrigin_ with the point with minimum X/Y + lb = QDoubleVector2D(qInf(), qInf()); + for (const QList<QDoubleVector2D> &path: clippedPaths) { + for (const QDoubleVector2D &p: path) { + if (p.x() < lb.x() || (p.x() == lb.x() && p.y() < lb.y())) { + lb = p; + } + } + } + if (qIsInf(lb.x())) + return; + + // Prevent the conversion to and from clipper from introducing negative offsets which + // in turn will make the geometry wrap around. + lb.setX(qMax(tl.x(), lb.x())); + srcOrigin_ = map.geoProjection().mapProjectionToGeo(map.geoProjection().unwrapMapProjection(lb)); + } else { + clippedPaths = QClipperUtils::pathsToQList(difference); + } + + //3) + QDoubleVector2D origin = map.geoProjection().wrappedMapProjectionToItemPosition(lb); + + QPainterPath ppi; + for (const QList<QDoubleVector2D> &path: clippedPaths) { + QDoubleVector2D lastAddedPoint; + for (int i = 0; i < path.size(); ++i) { + QDoubleVector2D point = map.geoProjection().wrappedMapProjectionToItemPosition(path.at(i)); + //point = point - origin; // Do this using ppi.translate() + + if (i == 0) { + ppi.moveTo(point.toPointF()); + lastAddedPoint = point; + } else { + if ((point - lastAddedPoint).manhattanLength() > 3 || + i == path.size() - 1) { + ppi.lineTo(point.toPointF()); + lastAddedPoint = point; + } + } + } + ppi.closeSubpath(); + } + ppi.translate(-1 * origin.toPointF()); + + QTriangleSet ts = qTriangulate(ppi); + qreal *vx = ts.vertices.data(); + + screenIndices_.reserve(ts.indices.size()); + screenVertices_.reserve(ts.vertices.size()); + + if (ts.indices.type() == QVertexIndexVector::UnsignedInt) { + const quint32 *ix = reinterpret_cast<const quint32 *>(ts.indices.data()); + for (int i = 0; i < (ts.indices.size()/3*3); ++i) + screenIndices_ << ix[i]; + } else { + const quint16 *ix = reinterpret_cast<const quint16 *>(ts.indices.data()); + for (int i = 0; i < (ts.indices.size()/3*3); ++i) + screenIndices_ << ix[i]; + } + for (int i = 0; i < (ts.vertices.size()/2*2); i += 2) + screenVertices_ << QPointF(vx[i], vx[i + 1]); + + screenBounds_ = ppi.boundingRect(); + sourceBounds_ = screenBounds_; +} + +static bool crossEarthPole(const QGeoCoordinate ¢er, qreal distance) +{ + qreal poleLat = 90; + QGeoCoordinate northPole = QGeoCoordinate(poleLat, center.longitude()); + QGeoCoordinate southPole = QGeoCoordinate(-poleLat, center.longitude()); + // approximate using great circle distance + qreal distanceToNorthPole = center.distanceTo(northPole); + qreal distanceToSouthPole = center.distanceTo(southPole); + if (distanceToNorthPole < distance || distanceToSouthPole < distance) + return true; + return false; +} + +static void calculatePeripheralPoints(QList<QGeoCoordinate> &path, + const QGeoCoordinate ¢er, + qreal distance, + int steps) +{ + // Calculate points based on great-circle distance + // Calculation is the same as GeoCoordinate's atDistanceAndAzimuth function + // but tweaked here for computing multiple points + + // pre-calculations + steps = qMax(steps, 3); + qreal centerLon = center.longitude(); + qreal latRad = QLocationUtils::radians(center.latitude()); + qreal lonRad = QLocationUtils::radians(centerLon); + qreal cosLatRad = std::cos(latRad); + qreal sinLatRad = std::sin(latRad); + qreal ratio = (distance / (QLocationUtils::earthMeanRadius())); + qreal cosRatio = std::cos(ratio); + qreal sinRatio = std::sin(ratio); + qreal sinLatRad_x_cosRatio = sinLatRad * cosRatio; + qreal cosLatRad_x_sinRatio = cosLatRad * sinRatio; + for (int i = 0; i < steps; ++i) { + qreal azimuthRad = 2 * M_PI * i / steps; + qreal resultLatRad = std::asin(sinLatRad_x_cosRatio + + cosLatRad_x_sinRatio * std::cos(azimuthRad)); + qreal resultLonRad = lonRad + std::atan2(std::sin(azimuthRad) * cosLatRad_x_sinRatio, + cosRatio - sinLatRad * std::sin(resultLatRad)); + qreal lat2 = QLocationUtils::degrees(resultLatRad); + qreal lon2 = QLocationUtils::wrapLong(QLocationUtils::degrees(resultLonRad)); + + path << QGeoCoordinate(lat2, lon2, center.altitude()); + } +} + +QDeclarativeCircleMapItem::QDeclarativeCircleMapItem(QQuickItem *parent) +: QDeclarativeGeoMapItemBase(parent), border_(this), color_(Qt::transparent), dirtyMaterial_(true), + updatingGeometry_(false) +{ + setFlag(ItemHasContents, true); + QObject::connect(&border_, SIGNAL(colorChanged(QColor)), + this, SLOT(markSourceDirtyAndUpdate())); + QObject::connect(&border_, SIGNAL(widthChanged(qreal)), + this, SLOT(markSourceDirtyAndUpdate())); + + // assume that circles are not self-intersecting + // to speed up processing + // FIXME: unfortunately they self-intersect at the poles due to current drawing method + // so the line is commented out until fixed + //geometry_.setAssumeSimple(true); + +} + +QDeclarativeCircleMapItem::~QDeclarativeCircleMapItem() +{ +} + +/*! + \qmlpropertygroup Location::MapCircle::border + \qmlproperty int MapCircle::border.width + \qmlproperty color MapCircle::border.color + + This property is part of the border group property. + The border property holds the width and color used to draw the border of the circle. + The width is in pixels and is independent of the zoom level of the map. + + The default values correspond to a black border with a width of 1 pixel. + For no line, use a width of 0 or a transparent color. +*/ +QDeclarativeMapLineProperties *QDeclarativeCircleMapItem::border() +{ + return &border_; +} + +void QDeclarativeCircleMapItem::markSourceDirtyAndUpdate() +{ + geometry_.markSourceDirty(); + borderGeometry_.markSourceDirty(); + polishAndUpdate(); +} + +void QDeclarativeCircleMapItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) +{ + QDeclarativeGeoMapItemBase::setMap(quickMap,map); + if (!map) + return; + updateCirclePath(); + markSourceDirtyAndUpdate(); +} + +/*! + \qmlproperty coordinate MapCircle::center + + This property holds the central point about which the circle is defined. + + \sa radius +*/ +void QDeclarativeCircleMapItem::setCenter(const QGeoCoordinate ¢er) +{ + if (circle_.center() == center) + return; + + circle_.setCenter(center); + updateCirclePath(); + markSourceDirtyAndUpdate(); + emit centerChanged(center); +} + +QGeoCoordinate QDeclarativeCircleMapItem::center() +{ + return circle_.center(); +} + +/*! + \qmlproperty color MapCircle::color + + This property holds the fill color of the circle when drawn. For no fill, + use a transparent color. +*/ +void QDeclarativeCircleMapItem::setColor(const QColor &color) +{ + if (color_ == color) + return; + color_ = color; + dirtyMaterial_ = true; + update(); + emit colorChanged(color_); +} + +QColor QDeclarativeCircleMapItem::color() const +{ + return color_; +} + +/*! + \qmlproperty real MapCircle::radius + + This property holds the radius of the circle, in meters on the ground. + + \sa center +*/ +void QDeclarativeCircleMapItem::setRadius(qreal radius) +{ + if (circle_.radius() == radius) + return; + + circle_.setRadius(radius); + updateCirclePath(); + markSourceDirtyAndUpdate(); + emit radiusChanged(radius); +} + +qreal QDeclarativeCircleMapItem::radius() const +{ + return circle_.radius(); +} + +/*! + \qmlproperty real MapCircle::opacity + + This property holds the opacity of the item. Opacity is specified as a + number between 0 (fully transparent) and 1 (fully opaque). The default is 1. + + An item with 0 opacity will still receive mouse events. To stop mouse events, set the + visible property of the item to false. +*/ + +/*! + \internal +*/ +QSGNode *QDeclarativeCircleMapItem::updateMapItemPaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) +{ + Q_UNUSED(data); + + MapPolygonNode *node = static_cast<MapPolygonNode *>(oldNode); + + if (!node) + node = new MapPolygonNode(); + + //TODO: update only material + if (geometry_.isScreenDirty() || borderGeometry_.isScreenDirty() || dirtyMaterial_) { + node->update(color_, border_.color(), &geometry_, &borderGeometry_); + geometry_.setPreserveGeometry(false); + borderGeometry_.setPreserveGeometry(false); + geometry_.markClean(); + borderGeometry_.markClean(); + dirtyMaterial_ = false; + } + return node; +} + +/*! + \internal +*/ +void QDeclarativeCircleMapItem::updatePolish() +{ + if (!map() || !circle_.isValid()) + return; + + QScopedValueRollback<bool> rollback(updatingGeometry_); + updatingGeometry_ = true; + + QList<QDoubleVector2D> circlePath = circlePath_; + + int pathCount = circlePath.size(); + bool preserve = preserveCircleGeometry(circlePath, circle_.center(), circle_.radius()); + geometry_.setPreserveGeometry(true, circle_.boundingGeoRectangle().topLeft()); // to set the geoLeftBound_ + geometry_.setPreserveGeometry(preserve, circle_.boundingGeoRectangle().topLeft()); + + bool invertedCircle = false; + if (crossEarthPole(circle_.center(), circle_.radius()) && circlePath.size() == pathCount) { + geometry_.updateScreenPointsInvert(circlePath, *map()); // invert fill area for really huge circles + invertedCircle = true; + } else { + geometry_.updateSourcePoints(*map(), circlePath); + geometry_.updateScreenPoints(*map()); + } + + borderGeometry_.clear(); + QList<QGeoMapItemGeometry *> geoms; + geoms << &geometry_; + + if (border_.color() != Qt::transparent && border_.width() > 0) { + QList<QDoubleVector2D> closedPath = circlePath; + closedPath << closedPath.first(); + + if (invertedCircle) { + closedPath = circlePath_; + closedPath << closedPath.first(); + std::reverse(closedPath.begin(), closedPath.end()); + } + + borderGeometry_.setPreserveGeometry(true, circle_.boundingGeoRectangle().topLeft()); // to set the geoLeftBound_ + borderGeometry_.setPreserveGeometry(preserve, circle_.boundingGeoRectangle().topLeft()); + + // Use srcOrigin_ from fill geometry after clipping to ensure that translateToCommonOrigin won't fail. + const QGeoCoordinate &geometryOrigin = geometry_.origin(); + + borderGeometry_.srcPoints_.clear(); + borderGeometry_.srcPointTypes_.clear(); + + QDoubleVector2D borderLeftBoundWrapped; + QList<QList<QDoubleVector2D > > clippedPaths = borderGeometry_.clipPath(*map(), closedPath, borderLeftBoundWrapped); + if (clippedPaths.size()) { + borderLeftBoundWrapped = map()->geoProjection().geoToWrappedMapProjection(geometryOrigin); + borderGeometry_.pathToScreen(*map(), clippedPaths, borderLeftBoundWrapped); + borderGeometry_.updateScreenPoints(*map(), border_.width()); + geoms << &borderGeometry_; + } else { + borderGeometry_.clear(); + } + } + + QRectF combined = QGeoMapItemGeometry::translateToCommonOrigin(geoms); + setWidth(combined.width()); + setHeight(combined.height()); + + setPositionOnMap(geometry_.origin(), geometry_.firstPointOffset()); +} + +/*! + \internal +*/ +void QDeclarativeCircleMapItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event) +{ + if (event.mapSize.width() <= 0 || event.mapSize.height() <= 0) + return; + + markSourceDirtyAndUpdate(); +} + +/*! + \internal +*/ +void QDeclarativeCircleMapItem::updateCirclePath() +{ + if (!map()) + return; + QList<QGeoCoordinate> path; + calculatePeripheralPoints(path, circle_.center(), circle_.radius(), CircleSamples); + circlePath_.clear(); + for (const QGeoCoordinate &c : path) + circlePath_ << map()->geoProjection().geoToMapProjection(c); +} + +/*! + \internal +*/ +bool QDeclarativeCircleMapItem::contains(const QPointF &point) const +{ + return (geometry_.contains(point) || borderGeometry_.contains(point)); +} + +const QGeoShape &QDeclarativeCircleMapItem::geoShape() const +{ + return circle_; +} + +QGeoMap::ItemType QDeclarativeCircleMapItem::itemType() const +{ + return QGeoMap::MapCircle; +} + +/*! + \internal +*/ +void QDeclarativeCircleMapItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + if (!map() || !circle_.isValid() || updatingGeometry_ || newGeometry == oldGeometry) { + QDeclarativeGeoMapItemBase::geometryChanged(newGeometry, oldGeometry); + return; + } + + QDoubleVector2D newPoint = QDoubleVector2D(x(),y()) + QDoubleVector2D(width(), height()) / 2; + QGeoCoordinate newCoordinate = map()->geoProjection().itemPositionToCoordinate(newPoint, false); + if (newCoordinate.isValid()) + setCenter(newCoordinate); + + // Not calling QDeclarativeGeoMapItemBase::geometryChanged() as it will be called from a nested + // call to this function. +} + +bool QDeclarativeCircleMapItem::preserveCircleGeometry (QList<QDoubleVector2D> &path, + const QGeoCoordinate ¢er, qreal distance) +{ + // if circle crosses north/south pole, then don't preserve circular shape, + if ( crossEarthPole(center, distance)) { + updateCirclePathForRendering(path, center, distance); + return false; + } + return true; + +} + +/* + * A workaround for circle path to be drawn correctly using a polygon geometry + * This method generates a polygon like + * _____________ + * | | + * \ / + * | | + * / \ + * | | + * ------------- + * + * or a polygon like + * + * ______________ + * | ____ | + * \__/ \__/ + */ +void QDeclarativeCircleMapItem::updateCirclePathForRendering(QList<QDoubleVector2D> &path, + const QGeoCoordinate ¢er, + qreal distance) +{ + qreal poleLat = 90; + qreal distanceToNorthPole = center.distanceTo(QGeoCoordinate(poleLat, 0)); + qreal distanceToSouthPole = center.distanceTo(QGeoCoordinate(-poleLat, 0)); + bool crossNorthPole = distanceToNorthPole < distance; + bool crossSouthPole = distanceToSouthPole < distance; + + QList<int> wrapPathIndex; + QDoubleVector2D prev = map()->geoProjection().wrapMapProjection(path.at(0)); + + for (int i = 1; i <= path.count(); ++i) { + int index = i % path.count(); + QDoubleVector2D point = map()->geoProjection().wrapMapProjection(path.at(index)); + double diff = qAbs(point.x() - prev.x()); + if (diff > 0.5) { + continue; + } + } + + // find the points in path where wrapping occurs + for (int i = 1; i <= path.count(); ++i) { + int index = i % path.count(); + QDoubleVector2D point = map()->geoProjection().wrapMapProjection(path.at(index)); + if ( (qAbs(point.x() - prev.x())) >= 0.5 ) { + wrapPathIndex << index; + if (wrapPathIndex.size() == 2 || !(crossNorthPole && crossSouthPole)) + break; + } + prev = point; + } + // insert two additional coords at top/bottom map corners of the map for shape + // to be drawn correctly + if (wrapPathIndex.size() > 0) { + qreal newPoleLat = 0; // 90 latitude + QDoubleVector2D wrapCoord = path.at(wrapPathIndex[0]); + if (wrapPathIndex.size() == 2) { + QDoubleVector2D wrapCoord2 = path.at(wrapPathIndex[1]); + if (wrapCoord2.y() < wrapCoord.y()) + newPoleLat = 1; // -90 latitude + } else if (center.latitude() < 0) { + newPoleLat = 1; // -90 latitude + } + for (int i = 0; i < wrapPathIndex.size(); ++i) { + int index = wrapPathIndex[i] == 0 ? 0 : wrapPathIndex[i] + i*2; + int prevIndex = (index - 1) < 0 ? (path.count() - 1): index - 1; + QDoubleVector2D coord0 = path.at(prevIndex); + QDoubleVector2D coord1 = path.at(index); + coord0.setY(newPoleLat); + coord1.setY(newPoleLat); + path.insert(index ,coord1); + path.insert(index, coord0); + newPoleLat = 1.0 - newPoleLat; + } + } +} + +////////////////////////////////////////////////////////////////////// + +QT_END_NAMESPACE diff --git a/src/location/declarativemaps/qdeclarativecirclemapitem_p.h b/src/location/declarativemaps/qdeclarativecirclemapitem_p.h new file mode 100644 index 00000000..511e3b17 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativecirclemapitem_p.h @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVECIRCLEMAPITEM_H +#define QDECLARATIVECIRCLEMAPITEM_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtLocation/private/qlocationglobal_p.h> +#include <QtLocation/private/qdeclarativegeomapitembase_p.h> +#include <QtLocation/private/qdeclarativepolylinemapitem_p.h> +#include <QtLocation/private/qdeclarativepolygonmapitem_p.h> +#include <QSGGeometryNode> +#include <QSGFlatColorMaterial> +#include <QtPositioning/QGeoCircle> + +QT_BEGIN_NAMESPACE + +class QGeoMapCircleGeometry : public QGeoMapPolygonGeometry +{ +public: + QGeoMapCircleGeometry(); + + void updateScreenPointsInvert(const QList<QDoubleVector2D> &circlePath, const QGeoMap &map); +}; + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativeCircleMapItem : public QDeclarativeGeoMapItemBase +{ + Q_OBJECT + Q_PROPERTY(QGeoCoordinate center READ center WRITE setCenter NOTIFY centerChanged) + Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged) + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) + Q_PROPERTY(QDeclarativeMapLineProperties *border READ border CONSTANT) + +public: + explicit QDeclarativeCircleMapItem(QQuickItem *parent = 0); + ~QDeclarativeCircleMapItem(); + + virtual void setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) Q_DECL_OVERRIDE; + virtual QSGNode *updateMapItemPaintNode(QSGNode *, UpdatePaintNodeData *) Q_DECL_OVERRIDE; + + QGeoCoordinate center(); + void setCenter(const QGeoCoordinate ¢er); + + qreal radius() const; + void setRadius(qreal radius); + + QColor color() const; + void setColor(const QColor &color); + + QDeclarativeMapLineProperties *border(); + + bool contains(const QPointF &point) const Q_DECL_OVERRIDE; + const QGeoShape &geoShape() const Q_DECL_OVERRIDE; + QGeoMap::ItemType itemType() const Q_DECL_OVERRIDE; + +Q_SIGNALS: + void centerChanged(const QGeoCoordinate ¢er); + void radiusChanged(qreal radius); + void colorChanged(const QColor &color); + +protected: + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) Q_DECL_OVERRIDE; + void updatePolish() Q_DECL_OVERRIDE; + +protected Q_SLOTS: + void markSourceDirtyAndUpdate(); + virtual void afterViewportChanged(const QGeoMapViewportChangeEvent &event) Q_DECL_OVERRIDE; + +private: + void updateCirclePath(); + bool preserveCircleGeometry(QList<QDoubleVector2D> &path, const QGeoCoordinate ¢er, + qreal distance); + void updateCirclePathForRendering(QList<QDoubleVector2D> &path, const QGeoCoordinate ¢er, + qreal distance); + +private: + QGeoCircle circle_; + QDeclarativeMapLineProperties border_; + QColor color_; + QList<QDoubleVector2D> circlePath_; + bool dirtyMaterial_; + QGeoMapCircleGeometry geometry_; + QGeoMapPolylineGeometry borderGeometry_; + bool updatingGeometry_; +}; + +////////////////////////////////////////////////////////////////////// + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativeCircleMapItem) + +#endif /* QDECLARATIVECIRCLEMAPITEM_H */ diff --git a/src/location/declarativemaps/qdeclarativegeocodemodel.cpp b/src/location/declarativemaps/qdeclarativegeocodemodel.cpp new file mode 100644 index 00000000..f7422ec2 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeocodemodel.cpp @@ -0,0 +1,725 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativegeocodemodel_p.h" +#include "error_messages.h" + +#include <QtCore/QCoreApplication> +#include <QtQml/QQmlInfo> +#include <QtPositioning/QGeoCircle> +#include <QtLocation/QGeoServiceProvider> +#include <QtLocation/QGeoCodingManager> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype GeocodeModel + \instantiates QDeclarativeGeocodeModel + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-geocoding + \since Qt Location 5.5 + + \brief The GeocodeModel type provides support for searching operations + related to geographic information. + + The GeocodeModel type is used as part of a model/view grouping to + match addresses or search strings with geographic locations. How the + geographic locations generated are used or displayed is decided by any + Views attached to the GeocodeModel (for example a \l MapItemView or \l{ListView}). + + Like \l Map and \l RouteModel, all the data for a GeocodeModel to work + comes from a services plugin. This is contained in the \l{plugin} property, + and this must be set before the GeocodeModel can do any useful work. + + Once the plugin is set, the \l{query} property can be used to specify the + address or search string to match. If \l{autoUpdate} is enabled, the Model + will update its output automatically. Otherwise, the \l{update} method may + be used. By default, autoUpdate is disabled. + + The data stored and returned in the GeocodeModel consists of \l{Location} + objects, as a list with the role name "locationData". See the documentation + for \l{Location} for further details on its structure and contents. + + \section2 Example Usage + + The following snippet is two-part, showing firstly the declaration of + objects, and secondly a short piece of procedural code using it. We set + the geocodeModel's \l{autoUpdate} property to false, and call \l{update} once + the query is set up. In this case, as we use a string value in \l{query}, + only one update would occur, even with autoUpdate enabled. However, if we + provided an \l{Address} object we may inadvertently trigger multiple + requests whilst setting its properties. + + \code + Plugin { + id: aPlugin + } + + GeocodeModel { + id: geocodeModel + plugin: aPlugin + autoUpdate: false + } + \endcode + + \code + { + geocodeModel.query = "53 Brandl St, Eight Mile Plains, Australia" + geocodeModel.update() + } + \endcode +*/ + +/*! + \qmlsignal QtLocation::GeocodeModel::locationsChanged() + + This signal is emitted when locations in the model have changed. + + \sa count +*/ + + +QDeclarativeGeocodeModel::QDeclarativeGeocodeModel(QObject *parent) +: QAbstractListModel(parent), autoUpdate_(false), complete_(false), reply_(0), plugin_(0), + status_(QDeclarativeGeocodeModel::Null), error_(QDeclarativeGeocodeModel::NoError), + address_(0), limit_(-1), offset_(0) +{ +} + +QDeclarativeGeocodeModel::~QDeclarativeGeocodeModel() +{ + qDeleteAll(declarativeLocations_); + declarativeLocations_.clear(); + delete reply_; +} + +/*! + \internal + From QQmlParserStatus +*/ +void QDeclarativeGeocodeModel::componentComplete() +{ + complete_ = true; + if (autoUpdate_) + update(); +} + +/*! + \qmlmethod void QtLocation::GeocodeModel::update() + + Instructs the GeocodeModel to update its data. This is most useful + when \l autoUpdate is disabled, to force a refresh when the query + has been changed. +*/ +void QDeclarativeGeocodeModel::update() +{ + if (!complete_) + return; + + if (!plugin_) { + setError(EngineNotSetError, tr("Cannot geocode, plugin not set.")); + return; + } + + QGeoServiceProvider *serviceProvider = plugin_->sharedGeoServiceProvider(); + if (!serviceProvider) + return; + + QGeoCodingManager *geocodingManager = serviceProvider->geocodingManager(); + if (!geocodingManager) { + setError(EngineNotSetError, tr("Cannot geocode, geocode manager not set.")); + return; + } + if (!coordinate_.isValid() && (!address_ || address_->address().isEmpty()) && + (searchString_.isEmpty())) { + setError(ParseError, tr("Cannot geocode, valid query not set.")); + return; + } + abortRequest(); // abort possible previous requests + setError(NoError, QString()); + + if (coordinate_.isValid()) { + setStatus(QDeclarativeGeocodeModel::Loading); + reply_ = geocodingManager->reverseGeocode(coordinate_, boundingArea_); + if (reply_->isFinished()) { + if (reply_->error() == QGeoCodeReply::NoError) { + geocodeFinished(reply_); + } else { + geocodeError(reply_, reply_->error(), reply_->errorString()); + } + } + } else if (address_) { + setStatus(QDeclarativeGeocodeModel::Loading); + reply_ = geocodingManager->geocode(address_->address(), boundingArea_); + if (reply_->isFinished()) { + if (reply_->error() == QGeoCodeReply::NoError) { + geocodeFinished(reply_); + } else { + geocodeError(reply_, reply_->error(), reply_->errorString()); + } + } + } else if (!searchString_.isEmpty()) { + setStatus(QDeclarativeGeocodeModel::Loading); + reply_ = geocodingManager->geocode(searchString_, limit_, offset_, boundingArea_); + if (reply_->isFinished()) { + if (reply_->error() == QGeoCodeReply::NoError) { + geocodeFinished(reply_); + } else { + geocodeError(reply_, reply_->error(), reply_->errorString()); + } + } + } +} + +/*! + \internal +*/ +void QDeclarativeGeocodeModel::abortRequest() +{ + if (reply_) { + reply_->abort(); + reply_->deleteLater(); + reply_ = 0; + } +} + +/*! + \internal +*/ +void QDeclarativeGeocodeModel::queryContentChanged() +{ + if (autoUpdate_) + update(); +} + +/*! + \internal + From QAbstractListModel +*/ +int QDeclarativeGeocodeModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return declarativeLocations_.count(); +} + +/*! + \internal +*/ +QVariant QDeclarativeGeocodeModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + if (index.row() >= declarativeLocations_.count()) + return QVariant(); + if (role == QDeclarativeGeocodeModel::LocationRole) { + QObject *locationObject = declarativeLocations_.at(index.row()); + Q_ASSERT(locationObject); + return QVariant::fromValue(locationObject); + } + return QVariant(); +} + +QHash<int, QByteArray> QDeclarativeGeocodeModel::roleNames() const +{ + QHash<int, QByteArray> roleNames = QAbstractItemModel::roleNames(); + roleNames.insert(LocationRole, "locationData"); + return roleNames; +} + +/*! + \internal +*/ +void QDeclarativeGeocodeModel::setPlugin(QDeclarativeGeoServiceProvider *plugin) +{ + if (plugin_ == plugin) + return; + + reset(); // reset the model + plugin_ = plugin; + if (complete_) + emit pluginChanged(); + + if (!plugin) + return; + + if (plugin_->isAttached()) { + pluginReady(); + } else { + connect(plugin_, SIGNAL(attached()), + this, SLOT(pluginReady())); + } +} + +/*! + \internal +*/ +void QDeclarativeGeocodeModel::pluginReady() +{ + QGeoServiceProvider *serviceProvider = plugin_->sharedGeoServiceProvider(); + QGeoCodingManager *geocodingManager = serviceProvider->geocodingManager(); + + if (serviceProvider->error() != QGeoServiceProvider::NoError) { + QDeclarativeGeocodeModel::GeocodeError newError = UnknownError; + switch (serviceProvider->error()) { + case QGeoServiceProvider::NotSupportedError: + newError = EngineNotSetError; break; + case QGeoServiceProvider::UnknownParameterError: + newError = UnknownParameterError; break; + case QGeoServiceProvider::MissingRequiredParameterError: + newError = MissingRequiredParameterError; break; + case QGeoServiceProvider::ConnectionError: + newError = CommunicationError; break; + default: + break; + } + + setError(newError, serviceProvider->errorString()); + return; + } + + if (!geocodingManager) { + setError(EngineNotSetError,tr("Plugin does not support (reverse) geocoding.")); + return; + } + + connect(geocodingManager, SIGNAL(finished(QGeoCodeReply*)), + this, SLOT(geocodeFinished(QGeoCodeReply*))); + connect(geocodingManager, SIGNAL(error(QGeoCodeReply*,QGeoCodeReply::Error,QString)), + this, SLOT(geocodeError(QGeoCodeReply*,QGeoCodeReply::Error,QString))); +} + +/*! + \qmlproperty Plugin QtLocation::GeocodeModel::plugin + + This property holds the plugin that provides the actual geocoding service. + Note that all plugins do not necessarily provide geocoding (could for example provide + only routing or maps). + + \sa Plugin +*/ + +QDeclarativeGeoServiceProvider *QDeclarativeGeocodeModel::plugin() const +{ + return plugin_; +} + +void QDeclarativeGeocodeModel::setBounds(const QVariant &boundingArea) +{ + QGeoShape s; + + if (boundingArea.userType() == qMetaTypeId<QGeoRectangle>()) + s = boundingArea.value<QGeoRectangle>(); + else if (boundingArea.userType() == qMetaTypeId<QGeoCircle>()) + s = boundingArea.value<QGeoCircle>(); + else if (boundingArea.userType() == qMetaTypeId<QGeoShape>()) + s = boundingArea.value<QGeoShape>(); + + + if (boundingArea_ == s) + return; + + boundingArea_ = s; + emit boundsChanged(); +} + +/*! + \qmlproperty geoshape QtLocation::GeocodeModel::bounds + + This property holds the bounding area used to limit the results to those + within the area. This is particularly useful if query is only partially filled out, + as the service will attempt to (reverse) geocode all matches for the specified data. + + Accepted types are \l {georectangle} and + \l {geocircle}. +*/ +QVariant QDeclarativeGeocodeModel::bounds() const +{ + if (boundingArea_.type() == QGeoShape::RectangleType) + return QVariant::fromValue(QGeoRectangle(boundingArea_)); + else if (boundingArea_.type() == QGeoShape::CircleType) + return QVariant::fromValue(QGeoCircle(boundingArea_)); + else + return QVariant::fromValue(boundingArea_); +} + +void QDeclarativeGeocodeModel::geocodeFinished(QGeoCodeReply *reply) +{ + if (reply != reply_ || reply->error() != QGeoCodeReply::NoError) + return; + 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(); +} + +/*! + \internal +*/ +void QDeclarativeGeocodeModel::geocodeError(QGeoCodeReply *reply, + QGeoCodeReply::Error error, + const QString &errorString) +{ + if (reply != reply_) + return; + Q_UNUSED(error); + int oldCount = declarativeLocations_.count(); + if (oldCount > 0) { + // Reset the model + setLocations(reply->locations()); + emit locationsChanged(); + emit countChanged(); + } + setError(static_cast<QDeclarativeGeocodeModel::GeocodeError>(error), errorString); + setStatus(QDeclarativeGeocodeModel::Error); + reply->deleteLater(); + reply_ = 0; +} + +/*! + \qmlproperty enumeration QtLocation::GeocodeModel::status + + This read-only property holds the current status of the model. + + \list + \li GeocodeModel.Null - No geocode requests have been issued or \l reset has been called. + \li GeocodeModel.Ready - Geocode request(s) have finished successfully. + \li GeocodeModel.Loading - Geocode request has been issued but not yet finished + \li GeocodeModel.Error - Geocoding error has occurred, details are in \l error and \l errorString + \endlist +*/ + +QDeclarativeGeocodeModel::Status QDeclarativeGeocodeModel::status() const +{ + return status_; +} + +void QDeclarativeGeocodeModel::setStatus(QDeclarativeGeocodeModel::Status status) +{ + if (status_ == status) + return; + status_ = status; + emit statusChanged(); +} + +/*! + \qmlproperty enumeration QtLocation::GeocodeModel::error + + This read-only property holds the latest error value of the geocoding request. + + \list + \li GeocodeModel.NoError - No error has occurred. + \li GeocodeModel.CombinationError - An error occurred while results where being combined from multiple sources. + \li GeocodeModel.CommunicationError - An error occurred while communicating with the service provider. + \li GeocodeModel.EngineNotSetError - The model's plugin property was not set or there is no geocoding manager associated with the plugin. + \li GeocodeModel.MissingRequiredParameterError - A required parameter was not specified. + \li GeocodeModel.ParseError - The response from the service provider was in an unrecognizable format. + \li GeocodeModel.UnknownError - An error occurred which does not fit into any of the other categories. + \li GeocodeModel.UnknownParameterError - The plugin did not recognize one of the parameters it was given. + \li GeocodeModel.UnsupportedOptionError - The requested operation is not supported by the geocoding provider. + This may happen when the loaded engine does not support a particular geocoding request + such as reverse geocoding. + \endlist +*/ + +QDeclarativeGeocodeModel::GeocodeError QDeclarativeGeocodeModel::error() const +{ + return error_; +} + +void QDeclarativeGeocodeModel::setError(GeocodeError error, const QString &errorString) +{ + if (error_ == error && errorString_ == errorString) + return; + error_ = error; + errorString_ = errorString; + emit errorChanged(); +} + +/*! + \qmlproperty string QtLocation::GeocodeModel::errorString + + This read-only property holds the textual presentation of the latest geocoding error. + If no error has occurred or the model has been reset, an empty string is returned. + + An empty string may also be returned if an error occurred which has no associated + textual representation. +*/ + +QString QDeclarativeGeocodeModel::errorString() const +{ + return errorString_; +} + +/*! + \internal +*/ +void QDeclarativeGeocodeModel::setLocations(const QList<QGeoLocation> &locations) +{ + beginResetModel(); + qDeleteAll(declarativeLocations_); + declarativeLocations_.clear(); + for (int i = 0; i < locations.count(); ++i) { + QDeclarativeGeoLocation *location = new QDeclarativeGeoLocation(locations.at(i), this); + declarativeLocations_.append(location); + } + endResetModel(); +} + +/*! + \qmlproperty int QtLocation::GeocodeModel::count + + This property holds how many locations the model currently has. + Amongst other uses, you can use this value when accessing locations + via the GeocodeModel::get -method. +*/ + +int QDeclarativeGeocodeModel::count() const +{ + return declarativeLocations_.count(); +} + +/*! + \qmlmethod Location QtLocation::GeocodeModel::get(int) + + Returns the Location at given index. Use \l count property to check the + amount of locations available. The locations are indexed from zero, so the accessible range + is 0...(count - 1). + + If you access out of bounds, a zero (null object) is returned and a warning is issued. +*/ + +QDeclarativeGeoLocation *QDeclarativeGeocodeModel::get(int index) +{ + if (index < 0 || index >= declarativeLocations_.count()) { + qmlWarning(this) << QStringLiteral("Index '%1' out of range").arg(index); + return 0; + } + return declarativeLocations_.at(index); +} + +/*! + \qmlproperty int QtLocation::GeocodeModel::limit + + This property holds the maximum number of results. The limit and \l offset values are only + applicable with free string geocoding (that is they are not considered when using addresses + or coordinates in the search query). + + If limit is -1 the entire result set will be returned, otherwise at most limit results will be + returned. The limit and \l offset results can be used together to implement paging. +*/ + +int QDeclarativeGeocodeModel::limit() const +{ + return limit_; +} + +void QDeclarativeGeocodeModel::setLimit(int limit) +{ + if (limit == limit_) + return; + limit_ = limit; + if (autoUpdate_) { + update(); + } + emit limitChanged(); +} + +/*! + \qmlproperty int QtLocation::GeocodeModel::offset + + This property tells not to return the first 'offset' number of the results. The \l limit and + offset values are only applicable with free string geocoding (that is they are not considered + when using addresses or coordinates in the search query). + + The \l limit and offset results can be used together to implement paging. +*/ + +int QDeclarativeGeocodeModel::offset() const +{ + return offset_; +} + +void QDeclarativeGeocodeModel::setOffset(int offset) +{ + if (offset == offset_) + return; + offset_ = offset; + if (autoUpdate_) { + update(); + } + emit offsetChanged(); +} + +/*! + \qmlmethod void QtLocation::GeocodeModel::reset() + + Resets the model. All location data is cleared, any outstanding requests + are aborted and possible errors are cleared. Model status will be set + to GeocodeModel.Null +*/ + +void QDeclarativeGeocodeModel::reset() +{ + beginResetModel(); + if (!declarativeLocations_.isEmpty()) { + setLocations(QList<QGeoLocation>()); + emit countChanged(); + } + endResetModel(); + + abortRequest(); + setError(NoError, QString()); + setStatus(QDeclarativeGeocodeModel::Null); +} + +/*! + \qmlmethod void QtLocation::GeocodeModel::cancel() + + Cancels any outstanding requests and clears errors. Model status will be set to either + GeocodeModel.Null or GeocodeModel.Ready. +*/ +void QDeclarativeGeocodeModel::cancel() +{ + abortRequest(); + setError(NoError, QString()); + setStatus(declarativeLocations_.isEmpty() ? Null : Ready); +} + +/*! + \qmlproperty QVariant QtLocation::GeocodeModel::query + + This property holds the data of the geocoding request. + The property accepts three types of queries which determine both the data and + the type of action to be performed: + + \list + \li Address - Geocoding (address to coordinate) + \li \l {coordinate} - Reverse geocoding (coordinate to address) + \li string - Geocoding (address to coordinate) + \endlist +*/ + +QVariant QDeclarativeGeocodeModel::query() const +{ + return queryVariant_; +} + +void QDeclarativeGeocodeModel::setQuery(const QVariant &query) +{ + if (query == queryVariant_) + return; + + if (query.userType() == qMetaTypeId<QGeoCoordinate>()) { + if (address_) { + address_->disconnect(this); + address_ = 0; + } + searchString_.clear(); + + coordinate_ = query.value<QGeoCoordinate>(); + } else if (query.type() == QVariant::String) { + searchString_ = query.toString(); + if (address_) { + address_->disconnect(this); + address_ = 0; + } + coordinate_ = QGeoCoordinate(); + } else if (QObject *object = query.value<QObject *>()) { + if (QDeclarativeGeoAddress *address = qobject_cast<QDeclarativeGeoAddress *>(object)) { + if (address_) + address_->disconnect(this); + coordinate_ = QGeoCoordinate(); + searchString_.clear(); + + address_ = address; + connect(address_, SIGNAL(countryChanged()), this, SLOT(queryContentChanged())); + connect(address_, SIGNAL(countryCodeChanged()), this, SLOT(queryContentChanged())); + connect(address_, SIGNAL(stateChanged()), this, SLOT(queryContentChanged())); + connect(address_, SIGNAL(countyChanged()), this, SLOT(queryContentChanged())); + connect(address_, SIGNAL(cityChanged()), this, SLOT(queryContentChanged())); + connect(address_, SIGNAL(districtChanged()), this, SLOT(queryContentChanged())); + connect(address_, SIGNAL(streetChanged()), this, SLOT(queryContentChanged())); + connect(address_, SIGNAL(postalCodeChanged()), this, SLOT(queryContentChanged())); + } else { + qmlWarning(this) << QStringLiteral("Unsupported query type for geocode model ") + << QStringLiteral("(coordinate, string and Address supported)."); + return; + } + } else { + qmlWarning(this) << QStringLiteral("Unsupported query type for geocode model ") + << QStringLiteral("(coordinate, string and Address supported)."); + return; + } + + queryVariant_ = query; + emit queryChanged(); + if (autoUpdate_) + update(); +} + +/*! + \qmlproperty bool QtLocation::GeocodeModel::autoUpdate + + This property controls whether the Model automatically updates in response + to changes in its attached query. The default value of this property + is false. + + If setting this value to 'true' and using an Address or + \l {coordinate} as the query, note that any change at all in the + object's properties will trigger a new request to be sent. If you are adjusting many + properties of the object whilst autoUpdate is enabled, this can generate large numbers of + useless (and later discarded) requests. +*/ + +bool QDeclarativeGeocodeModel::autoUpdate() const +{ + return autoUpdate_; +} + +void QDeclarativeGeocodeModel::setAutoUpdate(bool update) +{ + if (autoUpdate_ == update) + return; + autoUpdate_ = update; + emit autoUpdateChanged(); +} + +QT_END_NAMESPACE diff --git a/src/location/declarativemaps/qdeclarativegeocodemodel_p.h b/src/location/declarativemaps/qdeclarativegeocodemodel_p.h new file mode 100644 index 00000000..6c8f533b --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeocodemodel_p.h @@ -0,0 +1,207 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEGEOCODEMODEL_H +#define QDECLARATIVEGEOCODEMODEL_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtLocation/private/qlocationglobal_p.h> +#include <QtLocation/private/qdeclarativegeoserviceprovider_p.h> + +#include <QtLocation/qgeocodereply.h> +#include <QtPositioning/private/qdeclarativegeoaddress_p.h> +#include <QtPositioning/private/qdeclarativegeolocation_p.h> + +#include <QtQml/qqml.h> +#include <QtQml/QQmlParserStatus> +#include <QAbstractListModel> +#include <QPointer> + + +QT_BEGIN_NAMESPACE + +class QGeoServiceProvider; +class QGeoCodingManager; +class QDeclarativeGeoLocation; + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativeGeocodeModel : public QAbstractListModel, public QQmlParserStatus +{ + Q_OBJECT + Q_ENUMS(Status) + Q_ENUMS(GeocodeError) + + Q_PROPERTY(QDeclarativeGeoServiceProvider *plugin READ plugin WRITE setPlugin NOTIFY pluginChanged) + Q_PROPERTY(bool autoUpdate READ autoUpdate WRITE setAutoUpdate NOTIFY autoUpdateChanged) + Q_PROPERTY(Status status READ status NOTIFY statusChanged) + Q_PROPERTY(QString errorString READ errorString NOTIFY errorChanged) + Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(int limit READ limit WRITE setLimit NOTIFY limitChanged) + Q_PROPERTY(int offset READ offset WRITE setOffset NOTIFY offsetChanged) + Q_PROPERTY(QVariant query READ query WRITE setQuery NOTIFY queryChanged) + Q_PROPERTY(QVariant bounds READ bounds WRITE setBounds NOTIFY boundsChanged) + Q_PROPERTY(GeocodeError error READ error NOTIFY errorChanged) + Q_INTERFACES(QQmlParserStatus) + +public: + enum Status { + Null, + Ready, + Loading, + Error + }; + + enum GeocodeError { + NoError = QGeoCodeReply::NoError, + EngineNotSetError = QGeoCodeReply::EngineNotSetError, //TODO Qt6 consider merge with NotSupportedError + CommunicationError = QGeoCodeReply::CommunicationError, //TODO Qt6 merge with Map's ConnectionError + ParseError = QGeoCodeReply::ParseError, + UnsupportedOptionError = QGeoCodeReply::UnsupportedOptionError, //TODO Qt6 consider rename UnsupportedOperationError + CombinationError = QGeoCodeReply::CombinationError, + UnknownError = QGeoCodeReply::UnknownError, + //we leave gap for future QGeoCodeReply errors + + //QGeoServiceProvider related errors start here + UnknownParameterError = 100, + MissingRequiredParameterError + }; + + enum Roles { + LocationRole = Qt::UserRole + 1 + }; + + explicit QDeclarativeGeocodeModel(QObject *parent = 0); + virtual ~QDeclarativeGeocodeModel(); + + // From QQmlParserStatus + virtual void classBegin() {} + virtual void componentComplete(); + + // From QAbstractListModel + virtual int rowCount(const QModelIndex &parent) const; + virtual QVariant data(const QModelIndex &index, int role) const; + virtual QHash<int,QByteArray> roleNames() const; + + void setPlugin(QDeclarativeGeoServiceProvider *plugin); + QDeclarativeGeoServiceProvider *plugin() const; + + void setBounds(const QVariant &boundingArea); + QVariant bounds() const; + + Status status() const; + QString errorString() const; + GeocodeError error() const; + + bool autoUpdate() const; + void setAutoUpdate(bool update); + + int count() const; + Q_INVOKABLE QDeclarativeGeoLocation *get(int index); + + int limit() const; + void setLimit(int limit); + int offset() const; + void setOffset(int offset); + + QVariant query() const; + void setQuery(const QVariant &query); + Q_INVOKABLE void reset(); + Q_INVOKABLE void cancel(); + +Q_SIGNALS: + void countChanged(); + void pluginChanged(); + void statusChanged(); + void errorChanged(); //emitted also for errorString notification + void locationsChanged(); + void autoUpdateChanged(); + void boundsChanged(); + void queryChanged(); + void limitChanged(); + void offsetChanged(); + +public Q_SLOTS: + void update(); + +protected Q_SLOTS: + void queryContentChanged(); + void geocodeFinished(QGeoCodeReply *reply); + void geocodeError(QGeoCodeReply *reply, + QGeoCodeReply::Error error, + const QString &errorString); + void pluginReady(); + +protected: + QGeoCodingManager *searchManager(); + void setStatus(Status status); + void setError(GeocodeError error, const QString &errorString); + bool autoUpdate_; + bool complete_; + +private: + void setLocations(const QList<QGeoLocation> &locations); + void abortRequest(); + QGeoCodeReply *reply_; + + QDeclarativeGeoServiceProvider *plugin_; + QGeoShape boundingArea_; + + QList<QDeclarativeGeoLocation *> declarativeLocations_; + + Status status_; + QString errorString_; + GeocodeError error_; + QVariant queryVariant_; + QGeoCoordinate coordinate_; + QDeclarativeGeoAddress *address_; + QString searchString_; + + int limit_; + int offset_; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/declarativemaps/qdeclarativegeomaneuver.cpp b/src/location/declarativemaps/qdeclarativegeomaneuver.cpp new file mode 100644 index 00000000..b1c67167 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeomaneuver.cpp @@ -0,0 +1,199 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativegeomaneuver_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \qmltype RouteManeuver + \instantiates QDeclarativeGeoManeuver + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-routing + \since Qt Location 5.5 + + \brief The RouteManeuver type represents the information relevant to the + point at which two RouteSegments meet. + + RouteSegment instances can be thought of as edges on a routing + graph, with RouteManeuver instances as optional labels attached to the + vertices of the graph. + + The most interesting information held in a RouteManeuver instance is + normally the textual navigation to provide and the position at which to + provide it, accessible by \l instructionText and \l position respectively. + + \section1 Example + + The following QML snippet demonstrates how to print information about a + route maneuver: + + \snippet declarative/routing.qml QtQuick import + \snippet declarative/maps.qml QtLocation import + \codeline + \snippet declarative/routing.qml RouteManeuver +*/ + +QDeclarativeGeoManeuver::QDeclarativeGeoManeuver(QObject *parent) + : QObject(parent) +{ +} + +QDeclarativeGeoManeuver::QDeclarativeGeoManeuver(const QGeoManeuver &maneuver, QObject *parent) + : QObject(parent), + maneuver_(maneuver) +{ +} + +QDeclarativeGeoManeuver::~QDeclarativeGeoManeuver() {} + +/*! + \qmlproperty bool RouteManeuver::valid + + This read-only property holds whether this maneuver is valid or not. + + Invalid maneuvers are used when there is no information + that needs to be attached to the endpoint of a QGeoRouteSegment instance. +*/ + +bool QDeclarativeGeoManeuver::valid() const +{ + return maneuver_.isValid(); +} + +/*! + \qmlproperty coordinate RouteManeuver::position + + This read-only property holds where the \l instructionText should be displayed. + +*/ + +QGeoCoordinate QDeclarativeGeoManeuver::position() const +{ + return maneuver_.position(); +} + +/*! + \qmlproperty string RouteManeuver::instructionText + + This read-only property holds textual navigation instruction. +*/ + +QString QDeclarativeGeoManeuver::instructionText() const +{ + return maneuver_.instructionText(); +} + +/*! + \qmlproperty enumeration RouteManeuver::direction + + Describes the change in direction associated with the instruction text + that is associated with a RouteManeuver. + + \list + \li RouteManeuver.NoDirection - There is no direction associated with the instruction text + \li RouteManeuver.DirectionForward - The instruction indicates that the direction of travel does not need to change + \li RouteManeuver.DirectionBearRight - The instruction indicates that the direction of travel should bear to the right + \li RouteManeuver.DirectionLightRight - The instruction indicates that a light turn to the right is required + \li RouteManeuver.DirectionRight - The instruction indicates that a turn to the right is required + \li RouteManeuver.DirectionHardRight - The instruction indicates that a hard turn to the right is required + \li RouteManeuver.DirectionUTurnRight - The instruction indicates that a u-turn to the right is required + \li RouteManeuver.DirectionUTurnLeft - The instruction indicates that a u-turn to the left is required + \li RouteManeuver.DirectionHardLeft - The instruction indicates that a hard turn to the left is required + \li RouteManeuver.DirectionLeft - The instruction indicates that a turn to the left is required + \li RouteManeuver.DirectionLightLeft - The instruction indicates that a light turn to the left is required + \li RouteManeuver.DirectionBearLeft - The instruction indicates that the direction of travel should bear to the left + \endlist +*/ + +QDeclarativeGeoManeuver::Direction QDeclarativeGeoManeuver::direction() const +{ + return QDeclarativeGeoManeuver::Direction(maneuver_.direction()); +} + +/*! + \qmlproperty int RouteManeuver::timeToNextInstruction + + This read-only property holds the estimated time it will take to travel + from the point at which the associated instruction was issued and the + point that the next instruction should be issued, in seconds. +*/ + +int QDeclarativeGeoManeuver::timeToNextInstruction() const +{ + return maneuver_.timeToNextInstruction(); +} + +/*! + \qmlproperty real RouteManeuver::distanceToNextInstruction + + This read-only property holds the distance, in meters, between the point at which + the associated instruction was issued and the point that the next instruction should + be issued. +*/ + +qreal QDeclarativeGeoManeuver::distanceToNextInstruction() const +{ + return maneuver_.distanceToNextInstruction(); +} + +/*! + \qmlproperty coordinate RouteManeuver::waypoint + + This property holds the waypoint associated with this maneuver. + All maneuvers do not have a waypoint associated with them, this + can be checked with \l waypointValid. + +*/ + +QGeoCoordinate QDeclarativeGeoManeuver::waypoint() const +{ + return maneuver_.waypoint(); +} + +/*! + \qmlproperty bool RouteManeuver::waypointValid + + This read-only property holds whether this \l waypoint, associated with this + maneuver, is valid or not. +*/ + +bool QDeclarativeGeoManeuver::waypointValid() const +{ + return maneuver_.waypoint().isValid(); +} + +QT_END_NAMESPACE diff --git a/src/location/declarativemaps/qdeclarativegeomaneuver_p.h b/src/location/declarativemaps/qdeclarativegeomaneuver_p.h new file mode 100644 index 00000000..0e957a1f --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeomaneuver_p.h @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEGEOMANEUVER_H +#define QDECLARATIVEGEOMANEUVER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtLocation/private/qlocationglobal_p.h> +#include <QtLocation/qgeomaneuver.h> + +#include <QtPositioning/QGeoCoordinate> + +#include <QObject> + + +QT_BEGIN_NAMESPACE + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativeGeoManeuver : public QObject +{ + Q_OBJECT + Q_ENUMS(Direction) + + Q_PROPERTY(bool valid READ valid CONSTANT) + Q_PROPERTY(QGeoCoordinate position READ position CONSTANT) + Q_PROPERTY(QString instructionText READ instructionText CONSTANT) + Q_PROPERTY(Direction direction READ direction CONSTANT) + Q_PROPERTY(int timeToNextInstruction READ timeToNextInstruction CONSTANT) + Q_PROPERTY(qreal distanceToNextInstruction READ distanceToNextInstruction CONSTANT) + Q_PROPERTY(QGeoCoordinate waypoint READ waypoint CONSTANT) + Q_PROPERTY(bool waypointValid READ waypointValid CONSTANT) + +public: + enum Direction { + NoDirection = QGeoManeuver::NoDirection, + DirectionForward = QGeoManeuver::DirectionForward, + DirectionBearRight = QGeoManeuver::DirectionBearRight, + DirectionLightRight = QGeoManeuver::DirectionLightRight, + DirectionRight = QGeoManeuver::DirectionRight, + DirectionHardRight = QGeoManeuver::DirectionHardRight, + DirectionUTurnRight = QGeoManeuver::DirectionUTurnRight, + DirectionUTurnLeft = QGeoManeuver::DirectionUTurnLeft, + DirectionHardLeft = QGeoManeuver::DirectionHardLeft, + DirectionLeft = QGeoManeuver::DirectionLeft, + DirectionLightLeft = QGeoManeuver::DirectionLightLeft, + DirectionBearLeft = QGeoManeuver::DirectionBearLeft + }; + + explicit QDeclarativeGeoManeuver(QObject *parent = 0); + QDeclarativeGeoManeuver(const QGeoManeuver &maneuver, QObject *parent = 0); + ~QDeclarativeGeoManeuver(); + + bool valid() const; + bool waypointValid() const; + + QGeoCoordinate position() const; + QString instructionText() const; + Direction direction() const; + int timeToNextInstruction() const; + qreal distanceToNextInstruction() const; + QGeoCoordinate waypoint() const; + +private: + QGeoManeuver maneuver_; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/declarativemaps/qdeclarativegeomap.cpp b/src/location/declarativemaps/qdeclarativegeomap.cpp new file mode 100644 index 00000000..1638d1c8 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeomap.cpp @@ -0,0 +1,2152 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativegeomap_p.h" +#include "qdeclarativegeomapquickitem_p.h" +#include "qdeclarativegeomapcopyrightsnotice_p.h" +#include "qdeclarativegeoserviceprovider_p.h" +#include "qdeclarativegeomaptype_p.h" +#include "qgeomappingmanager_p.h" +#include "qgeocameracapabilities_p.h" +#include "qgeomap_p.h" +#include "qdeclarativegeomapparameter_p.h" +#include <QtPositioning/QGeoCircle> +#include <QtPositioning/QGeoRectangle> +#include <QtQuick/QQuickWindow> +#include <QtQuick/QSGRectangleNode> +#include <QtQuick/private/qquickwindow_p.h> +#include <QtQml/qqmlinfo.h> +#include <cmath> + +#ifndef M_PI +#define M_PI 3.141592653589793238463 +#endif + + +QT_BEGIN_NAMESPACE + +/*! + \qmltype Map + \instantiates QDeclarativeGeoMap + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-maps + \since Qt Location 5.0 + + \brief The Map type displays a map. + + The Map type is used to display a map or image of the Earth, with + the capability to also display interactive objects tied to the map's + surface. + + There are a variety of different ways to visualize the Earth's surface + in a 2-dimensional manner, but all of them involve some kind of projection: + a mathematical relationship between the 3D coordinates (latitude, longitude + and altitude) and 2D coordinates (X and Y in pixels) on the screen. + + Different sources of map data can use different projections, and from the + point of view of the Map type, we treat these as one replaceable unit: + the Map plugin. A Map plugin consists of a data source, as well as all other + details needed to display its data on-screen. + + The current Map plugin in use is contained in the \l plugin property of + the Map item. In order to display any image in a Map item, you will need + to set this property. See the \l Plugin type for a description of how + to retrieve an appropriate plugin for use. + + The geographic region displayed in the Map item is referred to as its + viewport, and this is defined by the properties \l center, and + \l zoomLevel. The \l center property contains a \l {coordinate} + specifying the center of the viewport, while \l zoomLevel controls the scale of the + map. See each of these properties for further details about their values. + + When the map is displayed, each possible geographic coordinate that is + visible will map to some pixel X and Y coordinate on the screen. To perform + conversions between these two, Map provides the \l toCoordinate and + \l fromCoordinate functions, which are of general utility. + + \section2 Map Objects + + Map related objects can be declared within the body of a Map object in Qt Quick and will + automatically appear on the Map. To add objects programmatically, first be + sure they are created with the Map as their parent (for example in an argument to + Component::createObject), and then call the \l addMapItem method on the Map. + A corresponding \l removeMapItem method also exists to do the opposite and + remove an object from the Map. + + Moving Map objects around, resizing them or changing their shape normally + does not involve any special interaction with Map itself -- changing these + details about a map object will automatically update the display. + + \section2 Interaction + + The Map type includes support for pinch and flick gestures to control + zooming and panning. These are enabled by default, and available at any + time by using the \l gesture object. The actual GestureArea is constructed + specially at startup and cannot be replaced or destroyed. Its properties + can be altered, however, to control its behavior. + + \section2 Performance + + Maps are rendered using OpenGL (ES) and the Qt Scene Graph stack, and as + a result perform quite well where GL accelerated hardware is available. + + For "online" Map plugins, network bandwidth and latency can be major + contributors to the user's perception of performance. Extensive caching is + performed to mitigate this, but such mitigation is not always perfect. For + "offline" plugins, the time spent retrieving the stored geographic data + and rendering the basic map features can often play a dominant role. Some + offline plugins may use hardware acceleration themselves to (partially) + avert this. + + In general, large and complex Map items such as polygons and polylines with + large numbers of vertices can have an adverse effect on UI performance. + Further, more detailed notes on this are in the documentation for each + map item type. + + \section2 Example Usage + + The following snippet shows a simple Map and the necessary Plugin type + to use it. The map is centered over Oslo, Norway, with zoom level 14. + + \quotefromfile minimal_map/main.qml + \skipto import + \printuntil } + \printline } + \skipto Map + \printuntil } + \printline } + + \image minimal_map.png +*/ + +/*! + \qmlsignal QtLocation::Map::copyrightLinkActivated(string link) + + This signal is emitted when the user clicks on a \a link in the copyright notice. The + application should open the link in a browser or display its contents to the user. +*/ + +QDeclarativeGeoMap::QDeclarativeGeoMap(QQuickItem *parent) + : QQuickItem(parent), + m_plugin(0), + m_mappingManager(0), + m_activeMapType(0), + m_gestureArea(new QQuickGeoMapGestureArea(this)), + m_map(0), + m_error(QGeoServiceProvider::NoError), + m_color(QColor::fromRgbF(0.9, 0.9, 0.9)), + m_componentCompleted(false), + m_pendingFitViewport(false), + m_copyrightsVisible(true), + m_maximumViewportLatitude(0.0), + m_initialized(false), + m_userMinimumZoomLevel(qQNaN()), + m_userMaximumZoomLevel(qQNaN()), + m_userMinimumTilt(qQNaN()), + m_userMaximumTilt(qQNaN()), + m_userMinimumFieldOfView(qQNaN()), + m_userMaximumFieldOfView(qQNaN()) +{ + setAcceptHoverEvents(false); + setAcceptedMouseButtons(Qt::LeftButton); + 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"), false, false, 0), this); + m_cameraData.setCenter(QGeoCoordinate(51.5073,-0.1277)); //London city center + m_cameraData.setZoomLevel(8.0); + + m_cameraCapabilities.setTileSize(256); + m_cameraCapabilities.setSupportsBearing(true); + m_cameraCapabilities.setSupportsTilting(true); + m_cameraCapabilities.setMinimumZoomLevel(0); + m_cameraCapabilities.setMaximumZoomLevel(30); + m_cameraCapabilities.setMinimumTilt(0); + m_cameraCapabilities.setMaximumTilt(89.5); + m_cameraCapabilities.setMinimumFieldOfView(1); + m_cameraCapabilities.setMaximumFieldOfView(179); + + m_minimumTilt = m_cameraCapabilities.minimumTilt(); + m_maximumTilt = m_cameraCapabilities.maximumTilt(); + m_minimumFieldOfView = m_cameraCapabilities.minimumFieldOfView(); + m_maximumFieldOfView = m_cameraCapabilities.maximumFieldOfView(); +} + +QDeclarativeGeoMap::~QDeclarativeGeoMap() +{ + // Removing map parameters and map items from m_map + if (m_map) { + m_map->clearParameters(); + m_map->clearMapItems(); + } + + if (!m_mapViews.isEmpty()) + qDeleteAll(m_mapViews); + // remove any map items associations + for (int i = 0; i < m_mapItems.count(); ++i) { + if (m_mapItems.at(i)) + m_mapItems.at(i).data()->setMap(0,0); + } + m_mapItems.clear(); + + for (auto g: qAsConst(m_mapItemGroups)) { + if (!g) + continue; + const QList<QQuickItem *> quickKids = g->childItems(); + for (auto c: quickKids) { + QDeclarativeGeoMapItemBase *itemBase = qobject_cast<QDeclarativeGeoMapItemBase *>(c); + if (itemBase) + itemBase->setMap(0,0); + } + } + m_mapItemGroups.clear(); + + delete m_copyrights.data(); + m_copyrights.clear(); +} + +/*! + \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.data(), SIGNAL(copyrightsChanged(QImage)), + copyrights, SLOT(copyrightsChanged(QImage))); + connect(m_map.data(), SIGNAL(copyrightsChanged(QImage)), + this, SIGNAL(copyrightsChanged(QImage))); + + connect(m_map.data(), SIGNAL(copyrightsChanged(QString)), + copyrights, SLOT(copyrightsChanged(QString))); + connect(m_map.data(), SIGNAL(copyrightsChanged(QString)), + this, SIGNAL(copyrightsChanged(QString))); + + connect(m_map.data(), 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) + if (types[i]->mapType() == type) + return types[i]; + return Q_NULLPTR; +} + +void QDeclarativeGeoMap::onSupportedMapTypesChanged() +{ + QList<QDeclarativeGeoMapType *> supportedMapTypes; + QList<QGeoMapType> types = m_mappingManager->supportedMapTypes(); + for (int i = 0; i < types.size(); ++i) { + // types that are present and get removed will be deleted at QObject destruction + QDeclarativeGeoMapType *type = findMapType(m_supportedMapTypes, types[i]); + if (!type) + type = new QDeclarativeGeoMapType(types[i], this); + supportedMapTypes.append(type); + } + m_supportedMapTypes.swap(supportedMapTypes); + if (m_supportedMapTypes.isEmpty()) { + m_map->setActiveMapType(QGeoMapType()); // no supported map types: setting an invalid one + } else { + bool hasMapType = false; + foreach (QDeclarativeGeoMapType *declarativeType, m_supportedMapTypes) { + if (declarativeType->mapType() == m_map->activeMapType()) + hasMapType = true; + } + if (!hasMapType) { + QDeclarativeGeoMapType *type = m_supportedMapTypes.at(0); + m_activeMapType = type; + m_map->setActiveMapType(type->mapType()); + } + } + + emit supportedMapTypesChanged(); +} + +void QDeclarativeGeoMap::setError(QGeoServiceProvider::Error error, const QString &errorString) +{ + if (m_error == error && m_errorString == errorString) + return; + m_error = error; + m_errorString = errorString; + emit errorChanged(); +} + +/*! + \internal + Called when the mapping manager is initialized AND the declarative element has a valid size > 0 +*/ +void QDeclarativeGeoMap::initialize() +{ + // try to keep change signals in the end + bool centerHasChanged = false; + bool bearingHasChanged = false; + bool tiltHasChanged = false; + bool fovHasChanged = false; + + QGeoCoordinate center = m_cameraData.center(); + + if (qIsNaN(m_userMinimumZoomLevel)) + setMinimumZoomLevel(m_map->minimumZoom(), false); + else + setMinimumZoomLevel(qMax<qreal>(m_map->minimumZoom(), m_userMinimumZoomLevel), false); + + double bearing = m_cameraData.bearing(); + double tilt = m_cameraData.tilt(); + double fov = m_cameraData.fieldOfView(); // Must be 45.0 + + if (!m_cameraCapabilities.supportsBearing() && bearing != 0.0) { + m_cameraData.setBearing(0); + bearingHasChanged = true; + } + if (!m_cameraCapabilities.supportsTilting() && tilt != 0.0) { + m_cameraData.setTilt(0); + tiltHasChanged = true; + } + + m_cameraData.setFieldOfView(qBound(m_cameraCapabilities.minimumFieldOfView(), + fov, + m_cameraCapabilities.maximumFieldOfView())); + if (fov != m_cameraData.fieldOfView()) + fovHasChanged = true; + + // set latitude boundary check + m_maximumViewportLatitude = m_map->maximumCenterLatitudeAtZoom(m_cameraData); + + center.setLatitude(qBound(-m_maximumViewportLatitude, center.latitude(), m_maximumViewportLatitude)); + + if (center != m_cameraData.center()) { + centerHasChanged = true; + m_cameraData.setCenter(center); + } + + m_map->setCameraData(m_cameraData); + + m_initialized = true; + + if (centerHasChanged) + emit centerChanged(m_cameraData.center()); + + if (bearingHasChanged) + emit bearingChanged(m_cameraData.bearing()); + + if (tiltHasChanged) + emit tiltChanged(m_cameraData.tilt()); + + if (fovHasChanged) + emit fieldOfViewChanged(m_cameraData.fieldOfView()); + + emit mapReadyChanged(true); + + if (m_copyrights) + update(); +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::pluginReady() +{ + QGeoServiceProvider *provider = m_plugin->sharedGeoServiceProvider(); + m_mappingManager = provider->mappingManager(); + + if (provider->error() != QGeoServiceProvider::NoError) { + setError(provider->error(), provider->errorString()); + return; + } + + if (!m_mappingManager) { + //TODO Should really be EngineNotSetError (see QML GeoCodeModel) + setError(QGeoServiceProvider::NotSupportedError, tr("Plugin does not support mapping.")); + return; + } + + if (!m_mappingManager->isInitialized()) + connect(m_mappingManager, SIGNAL(initialized()), this, SLOT(mappingManagerInitialized())); + else + mappingManagerInitialized(); + + // make sure this is only called once + disconnect(this, SLOT(pluginReady())); +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::componentComplete() +{ + m_componentCompleted = true; + populateParameters(); + populateMap(); + QQuickItem::componentComplete(); +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::mousePressEvent(QMouseEvent *event) +{ + if (isInteractive()) + m_gestureArea->handleMousePressEvent(event); + else + QQuickItem::mousePressEvent(event); +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::mouseMoveEvent(QMouseEvent *event) +{ + if (isInteractive()) + m_gestureArea->handleMouseMoveEvent(event); + else + QQuickItem::mouseMoveEvent(event); +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::mouseReleaseEvent(QMouseEvent *event) +{ + if (isInteractive()) { + m_gestureArea->handleMouseReleaseEvent(event); + } else { + QQuickItem::mouseReleaseEvent(event); + } +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::mouseUngrabEvent() +{ + if (isInteractive()) + m_gestureArea->handleMouseUngrabEvent(); + else + QQuickItem::mouseUngrabEvent(); +} + +void QDeclarativeGeoMap::touchUngrabEvent() +{ + if (isInteractive()) + m_gestureArea->handleTouchUngrabEvent(); + else + QQuickItem::touchUngrabEvent(); +} + +/*! + \qmlproperty MapGestureArea QtLocation::Map::gesture + + Contains the MapGestureArea created with the Map. This covers pan, flick and pinch gestures. + Use \c{gesture.enabled: true} to enable basic gestures, or see \l{MapGestureArea} for + further details. +*/ + +QQuickGeoMapGestureArea *QDeclarativeGeoMap::gesture() +{ + return m_gestureArea; +} + +/*! + \internal + + This may happen before mappingManagerInitialized() +*/ +void QDeclarativeGeoMap::populateMap() +{ + QObjectList kids = children(); + QList<QQuickItem *> quickKids = childItems(); + for (int i=0; i < quickKids.count(); ++i) + kids.append(quickKids.at(i)); + + for (int i = 0; i < kids.size(); ++i) { + // dispatch items appropriately + QDeclarativeGeoMapItemView *mapView = qobject_cast<QDeclarativeGeoMapItemView *>(kids.at(i)); + if (mapView) { + m_mapViews.append(mapView); + setupMapView(mapView); + continue; + } + QDeclarativeGeoMapItemBase *mapItem = qobject_cast<QDeclarativeGeoMapItemBase *>(kids.at(i)); + if (mapItem) { + addMapItem(mapItem); + continue; + } + // Allow to add to the map Map items contained inside a parent QQuickItem, but only those at one level of nesting. + QDeclarativeGeoMapItemGroup *itemGroup = qobject_cast<QDeclarativeGeoMapItemGroup *>(kids.at(i)); + if (itemGroup) { + addMapItemGroup(itemGroup); + continue; + } + } +} + +void QDeclarativeGeoMap::populateParameters() +{ + QObjectList kids = children(); + QList<QQuickItem *> quickKids = childItems(); + for (int i = 0; i < quickKids.count(); ++i) + kids.append(quickKids.at(i)); + for (int i = 0; i < kids.size(); ++i) { + QDeclarativeGeoMapParameter *mapParameter = qobject_cast<QDeclarativeGeoMapParameter *>(kids.at(i)); + if (mapParameter) + addMapParameter(mapParameter); + } +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::setupMapView(QDeclarativeGeoMapItemView *view) +{ + Q_UNUSED(view) + view->setMap(this); + view->repopulate(); +} + +/*! + * \internal + */ +QSGNode *QDeclarativeGeoMap::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) +{ + if (!m_map) { + delete oldNode; + return 0; + } + + QSGRectangleNode *root = static_cast<QSGRectangleNode *>(oldNode); + if (!root) + root = window()->createRectangleNode(); + + root->setRect(boundingRect()); + root->setColor(m_color); + + QSGNode *content = root->childCount() ? root->firstChild() : 0; + content = m_map->updateSceneGraph(content, window()); + if (content && root->childCount() == 0) + root->appendChildNode(content); + + return root; +} + +/*! + \qmlproperty Plugin QtLocation::Map::plugin + + This property holds the plugin which provides the mapping functionality. + + This is a write-once property. Once the map has a plugin associated with + it, any attempted modifications of the plugin will be ignored. +*/ + +void QDeclarativeGeoMap::setPlugin(QDeclarativeGeoServiceProvider *plugin) +{ + if (m_plugin) { + qmlWarning(this) << QStringLiteral("Plugin is a write-once property, and cannot be set again."); + return; + } + m_plugin = plugin; + emit pluginChanged(m_plugin); + + if (m_plugin->isAttached()) { + pluginReady(); + } else { + connect(m_plugin, SIGNAL(attached()), + this, SLOT(pluginReady())); + } +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::onCameraCapabilitiesChanged(const QGeoCameraCapabilities &oldCameraCapabilities) +{ + if (m_map->cameraCapabilities() == oldCameraCapabilities) + return; + m_cameraCapabilities = m_map->cameraCapabilities(); + + //The zoom level limits are only restricted by the plugins values, if the user has set a more + //strict zoom level limit before initialization nothing is done here. + //minimum zoom level might be changed to limit gray bundaries + //This code assumes that plugins' maximum zoom level will never exceed 30.0 + if (m_cameraCapabilities.maximumZoomLevelAt256() < m_gestureArea->maximumZoomLevel()) { + setMaximumZoomLevel(m_cameraCapabilities.maximumZoomLevelAt256(), false); + } else if (m_cameraCapabilities.maximumZoomLevelAt256() > m_gestureArea->maximumZoomLevel()) { + if (qIsNaN(m_userMaximumZoomLevel)) { + // If the user didn't set anything + setMaximumZoomLevel(m_cameraCapabilities.maximumZoomLevelAt256(), false); + } else { // Try to set what the user requested + // Else if the user set something larger, but that got clamped by the previous camera caps + setMaximumZoomLevel(qMin<qreal>(m_cameraCapabilities.maximumZoomLevelAt256(), + m_userMaximumZoomLevel), false); + } + } + + if (m_cameraCapabilities.minimumZoomLevelAt256() > m_gestureArea->minimumZoomLevel()) { + setMinimumZoomLevel(m_cameraCapabilities.minimumZoomLevelAt256(), false); + } else if (m_cameraCapabilities.minimumZoomLevelAt256() < m_gestureArea->minimumZoomLevel()) { + if (qIsNaN(m_userMinimumZoomLevel)) { + // If the user didn't set anything, trying to set the new caps. + setMinimumZoomLevel(m_cameraCapabilities.minimumZoomLevelAt256(), false); + } else { // Try to set what the user requested + // Else if the user set a minimum, m_gestureArea->minimumZoomLevel() might be larger + // because of different reasons. Resetting it, as if it ends to be the same, + // no signal will be emitted. + setMinimumZoomLevel(qMax<qreal>(m_cameraCapabilities.minimumZoomLevelAt256(), + m_userMinimumZoomLevel), false); + } + } + + // Tilt + if (m_cameraCapabilities.maximumTilt() < m_maximumTilt) { + setMaximumTilt(m_cameraCapabilities.maximumTilt(), false); + } else if (m_cameraCapabilities.maximumTilt() > m_maximumTilt) { + if (qIsNaN(m_userMaximumTilt)) + setMaximumTilt(m_cameraCapabilities.maximumTilt(), false); + else // Try to set what the user requested + setMaximumTilt(qMin<qreal>(m_cameraCapabilities.maximumTilt(), m_userMaximumTilt), false); + } + + if (m_cameraCapabilities.minimumTilt() > m_minimumTilt) { + setMinimumTilt(m_cameraCapabilities.minimumTilt(), false); + } else if (m_cameraCapabilities.minimumTilt() < m_minimumTilt) { + if (qIsNaN(m_userMinimumTilt)) + setMinimumTilt(m_cameraCapabilities.minimumTilt(), false); + else // Try to set what the user requested + setMinimumTilt(qMax<qreal>(m_cameraCapabilities.minimumTilt(), m_userMinimumTilt), false); + } + + // FoV + if (m_cameraCapabilities.maximumFieldOfView() < m_maximumFieldOfView) { + setMaximumFieldOfView(m_cameraCapabilities.maximumFieldOfView(), false); + } else if (m_cameraCapabilities.maximumFieldOfView() > m_maximumFieldOfView) { + if (qIsNaN(m_userMaximumFieldOfView)) + setMaximumFieldOfView(m_cameraCapabilities.maximumFieldOfView(), false); + else // Try to set what the user requested + setMaximumFieldOfView(qMin<qreal>(m_cameraCapabilities.maximumFieldOfView(), m_userMaximumFieldOfView), false); + } + + if (m_cameraCapabilities.minimumFieldOfView() > m_minimumFieldOfView) { + setMinimumFieldOfView(m_cameraCapabilities.minimumFieldOfView(), false); + } else if (m_cameraCapabilities.minimumFieldOfView() < m_minimumFieldOfView) { + if (qIsNaN(m_userMinimumFieldOfView)) + setMinimumFieldOfView(m_cameraCapabilities.minimumFieldOfView(), false); + else // Try to set what the user requested + setMinimumFieldOfView(qMax<qreal>(m_cameraCapabilities.minimumFieldOfView(), m_userMinimumFieldOfView), false); + } +} + + +/*! + \internal + this function will only be ever called once +*/ +void QDeclarativeGeoMap::mappingManagerInitialized() +{ + m_map = QPointer<QGeoMap>(m_mappingManager->createMap(this)); + + if (!m_map) + return; + + m_gestureArea->setMap(m_map); + + QList<QGeoMapType> types = m_mappingManager->supportedMapTypes(); + for (int i = 0; i < types.size(); ++i) { + QDeclarativeGeoMapType *type = new QDeclarativeGeoMapType(types[i], this); + m_supportedMapTypes.append(type); + } + + if (!m_supportedMapTypes.isEmpty()) { + QDeclarativeGeoMapType *type = m_supportedMapTypes.at(0); + m_activeMapType = type; + m_map->setActiveMapType(type->mapType()); + } else { + m_map->setActiveMapType(m_activeMapType->mapType()); + } + + // Update camera capabilities + onCameraCapabilitiesChanged(m_cameraCapabilities); + + // Map tiles are built in this call. m_map->minimumZoom() becomes operational + // after this has been called at least once, after creation. + // However, getting into the following block may fire a copyrightsChanged that would get lost, + // as the connections are set up after. + QString copyrightString; + QImage copyrightImage; + if (!m_initialized && width() > 0 && height() > 0) { + QMetaObject::Connection copyrightStringCatcherConnection = + connect(m_map.data(), + QOverload<const QString &>::of(&QGeoMap::copyrightsChanged), + [©rightString](const QString ©){ copyrightString = copy; }); + QMetaObject::Connection copyrightImageCatcherConnection = + connect(m_map.data(), + QOverload<const QImage &>::of(&QGeoMap::copyrightsChanged), + [©rightImage](const QImage ©){ copyrightImage = copy; }); + m_map->setViewportSize(QSize(width(), height())); + initialize(); + QObject::disconnect(copyrightStringCatcherConnection); + QObject::disconnect(copyrightImageCatcherConnection); + } + + m_copyrights = new QDeclarativeGeoMapCopyrightNotice(this); + m_copyrights->onCopyrightsStyleSheetChanged(m_map->copyrightsStyleSheet()); + + connect(m_map.data(), SIGNAL(copyrightsChanged(QImage)), + m_copyrights.data(), SLOT(copyrightsChanged(QImage))); + connect(m_map.data(), SIGNAL(copyrightsChanged(QImage)), + this, SIGNAL(copyrightsChanged(QImage))); + + connect(m_map.data(), SIGNAL(copyrightsChanged(QString)), + m_copyrights.data(), SLOT(copyrightsChanged(QString))); + connect(m_map.data(), SIGNAL(copyrightsChanged(QString)), + this, SIGNAL(copyrightsChanged(QString))); + + if (!copyrightString.isEmpty()) + emit m_map.data()->copyrightsChanged(copyrightString); + else if (!copyrightImage.isNull()) + emit m_map.data()->copyrightsChanged(copyrightImage); + + connect(m_map.data(), SIGNAL(copyrightsStyleSheetChanged(QString)), + m_copyrights.data(), SLOT(onCopyrightsStyleSheetChanged(QString))); + + connect(m_copyrights.data(), SIGNAL(linkActivated(QString)), + this, SIGNAL(copyrightLinkActivated(QString))); + connect(m_map.data(), &QGeoMap::sgNodeChanged, this, &QQuickItem::update); + connect(m_map.data(), &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(); + + connect(m_mappingManager, SIGNAL(supportedMapTypesChanged()), this, SLOT(onSupportedMapTypesChanged())); + emit minimumZoomLevelChanged(); + emit maximumZoomLevelChanged(); + emit supportedMapTypesChanged(); + emit activeMapTypeChanged(); + + // Any map items that were added before the plugin was ready + // need to have setMap called again + for (const QPointer<QDeclarativeGeoMapItemBase> &item : qAsConst(m_mapItems)) { + if (item) { + item->setMap(this, m_map); + m_map->addMapItem(item.data()); // m_map filters out what is not supported. + } + } + + // Any map item groups that were added before the plugin was ready + // need to have setMap called again on their children map items + for (auto g: qAsConst(m_mapItemGroups)) { + const QList<QQuickItem *> quickKids = g->childItems(); + for (auto c: quickKids) { + QDeclarativeGeoMapItemBase *itemBase = qobject_cast<QDeclarativeGeoMapItemBase *>(c); + if (itemBase) + itemBase->setMap(this, m_map); + } + } + + // All map parameters that were added before the plugin was ready + // need to be added to m_map + for (QDeclarativeGeoMapParameter *p : qAsConst(m_mapParameters)) + m_map->addParameter(p); + + if (m_initialized) + update(); +} + +/*! + \internal +*/ +QDeclarativeGeoServiceProvider *QDeclarativeGeoMap::plugin() const +{ + return m_plugin; +} + +/*! + \internal + Sets the gesture areas minimum zoom level. If the camera capabilities + has been set this method honors the boundaries set by it. + The minimum zoom level will also have a lower bound dependent on the size + of the canvas, effectively preventing to display out of bounds areas. +*/ +void QDeclarativeGeoMap::setMinimumZoomLevel(qreal minimumZoomLevel, bool userSet) +{ + + if (minimumZoomLevel >= 0) { + if (userSet) + m_userMinimumZoomLevel = minimumZoomLevel; + qreal oldMinimumZoomLevel = this->minimumZoomLevel(); + + minimumZoomLevel = qBound(qreal(m_cameraCapabilities.minimumZoomLevelAt256()), minimumZoomLevel, maximumZoomLevel()); + if (m_map) + minimumZoomLevel = qMax<qreal>(minimumZoomLevel, m_map->minimumZoom()); + + m_gestureArea->setMinimumZoomLevel(minimumZoomLevel); + + if (zoomLevel() < minimumZoomLevel && (m_gestureArea->enabled() || !m_cameraCapabilities.overzoomEnabled())) + setZoomLevel(minimumZoomLevel); + + if (oldMinimumZoomLevel != minimumZoomLevel) + emit minimumZoomLevelChanged(); + } +} + +/*! + \qmlproperty real QtLocation::Map::minimumZoomLevel + + This property holds the minimum valid zoom level for the map. + + The minimum zoom level defined by the \l plugin used is a lower bound for + this property. However, the returned value is also canvas-size-dependent, and + can be higher than the user-specified value, or than the minimum zoom level + defined by the plugin used, to prevent the map from being smaller than the + viewport in either dimension. + + If the \l plugin property is not set or the plugin does not support mapping, this property is \c 0. +*/ + +qreal QDeclarativeGeoMap::minimumZoomLevel() const +{ + return m_gestureArea->minimumZoomLevel(); +} + +/*! + \internal + Sets the gesture areas maximum zoom level. If the camera capabilities + has been set this method honors the boundaries set by it. +*/ +void QDeclarativeGeoMap::setMaximumZoomLevel(qreal maximumZoomLevel, bool userSet) +{ + if (maximumZoomLevel >= 0) { + if (userSet) + m_userMaximumZoomLevel = maximumZoomLevel; + qreal oldMaximumZoomLevel = this->maximumZoomLevel(); + + maximumZoomLevel = qBound(minimumZoomLevel(), maximumZoomLevel, qreal(m_cameraCapabilities.maximumZoomLevelAt256())); + + m_gestureArea->setMaximumZoomLevel(maximumZoomLevel); + + if (zoomLevel() > maximumZoomLevel && (m_gestureArea->enabled() || !m_cameraCapabilities.overzoomEnabled())) + setZoomLevel(maximumZoomLevel); + + if (oldMaximumZoomLevel != maximumZoomLevel) + emit maximumZoomLevelChanged(); + } +} + +/*! + \qmlproperty real QtLocation::Map::maximumZoomLevel + + This property holds the maximum valid zoom level for the map. + + The maximum zoom level is defined by the \l plugin used. + If the \l plugin property is not set or the plugin does not support mapping, this property is \c 30. +*/ + +qreal QDeclarativeGeoMap::maximumZoomLevel() const +{ + return m_gestureArea->maximumZoomLevel(); +} + +/*! + \qmlproperty real QtLocation::Map::zoomLevel + + This property holds the zoom level for the map. + + Larger values for the zoom level provide more detail. Zoom levels + are always non-negative. The default value is 8.0. Depending on the plugin in use, + values outside the [minimumZoomLevel, maximumZoomLevel] range, which represent the range for which + tiles are available, may be accepted, or clamped. +*/ +void QDeclarativeGeoMap::setZoomLevel(qreal zoomLevel) +{ + if (m_cameraData.zoomLevel() == zoomLevel || zoomLevel < 0) + return; + + //small optimization to avoid double setCameraData + bool centerHasChanged = false; + + if (m_initialized) { + m_cameraData.setZoomLevel(qBound<qreal>(m_cameraCapabilities.overzoomEnabled() ? 0 : minimumZoomLevel(), zoomLevel, + m_cameraCapabilities.overzoomEnabled() ? 30 : maximumZoomLevel())); + m_maximumViewportLatitude = m_map->maximumCenterLatitudeAtZoom(m_cameraData); + QGeoCoordinate coord = m_cameraData.center(); + coord.setLatitude(qBound(-m_maximumViewportLatitude, coord.latitude(), m_maximumViewportLatitude)); + if (coord != m_cameraData.center()) { + centerHasChanged = true; + m_cameraData.setCenter(coord); + } + m_map->setCameraData(m_cameraData); + } else { + m_cameraData.setZoomLevel(zoomLevel); + } + + if (centerHasChanged) + emit centerChanged(m_cameraData.center()); + emit zoomLevelChanged(m_cameraData.zoomLevel()); +} + +qreal QDeclarativeGeoMap::zoomLevel() const +{ + return m_cameraData.zoomLevel(); +} + +/*! + \qmlproperty real QtLocation::Map::bearing + + This property holds the bearing for the map. + The default value is 0. + If the Plugin used for the Map supports bearing, the valid range for this value is between 0 and 360. + If the Plugin used for the Map does not support bearing, changing this property will have no effect. + + \since Qt Location 5.9 +*/ +void QDeclarativeGeoMap::setBearing(qreal bearing) +{ + bearing = std::fmod(bearing, qreal(360.0)); + if (bearing < 0.0) + bearing += 360.0; + if (m_map && !m_cameraCapabilities.supportsBearing()) + bearing = 0.0; + if (m_cameraData.bearing() == bearing || bearing < 0.0) + return; + + m_cameraData.setBearing(bearing); + if (m_map) + m_map->setCameraData(m_cameraData); + emit bearingChanged(bearing); +} + +qreal QDeclarativeGeoMap::bearing() const +{ + return m_cameraData.bearing(); +} + +/*! + \qmlproperty real QtLocation::Map::tilt + + This property holds the tilt for the map, in degrees. + The default value is 0. + The valid range for this value is [ minimumTilt, maximumTilt ]. + If the Plugin used for the Map does not support tilting, changing this property will have no effect. + + \sa minimumTilt, maximumTilt + + \since Qt Location 5.9 +*/ +void QDeclarativeGeoMap::setTilt(qreal tilt) +{ + tilt = qBound(minimumTilt(), tilt, maximumTilt()); + if (m_cameraData.tilt() == tilt) + return; + + m_cameraData.setTilt(tilt); + if (m_map) + m_map->setCameraData(m_cameraData); + emit tiltChanged(tilt); +} + +qreal QDeclarativeGeoMap::tilt() const +{ + return m_cameraData.tilt(); +} + +void QDeclarativeGeoMap::setMinimumTilt(qreal minimumTilt, bool userSet) +{ + if (minimumTilt >= 0) { + if (userSet) + m_userMinimumTilt = minimumTilt; + qreal oldMinimumTilt = this->minimumTilt(); + + m_minimumTilt = qBound(m_cameraCapabilities.minimumTilt(), + minimumTilt, + m_cameraCapabilities.maximumTilt()); + + if (tilt() < m_minimumTilt) + setTilt(m_minimumTilt); + + if (oldMinimumTilt != m_minimumTilt) + emit minimumTiltChanged(m_minimumTilt); + } +} + +/*! + \qmlproperty real QtLocation::Map::fieldOfView + + This property holds the field of view of the camera used to look at the map, in degrees. + If the plugin property of the map is not set, or the plugin does not support mapping, the value is 45 degrees. + + Note that changing this value implicitly changes also the distance between the camera and the map, + so that, at a tilting angle of 0 degrees, the resulting image is identical for any value used for this property. + + For more information about this parameter, consult the Wikipedia articles about \l {https://en.wikipedia.org/wiki/Field_of_view} {Field of view} and + \l {https://en.wikipedia.org/wiki/Angle_of_view} {Angle of view}. + + \sa minimumFieldOfView, maximumFieldOfView + + \since Qt Location 5.9 +*/ +void QDeclarativeGeoMap::setFieldOfView(qreal fieldOfView) +{ + fieldOfView = qBound(minimumFieldOfView(), fieldOfView, maximumFieldOfView()); + if (m_cameraData.fieldOfView() == fieldOfView) + return; + + m_cameraData.setFieldOfView(fieldOfView); + if (m_map) + m_map->setCameraData(m_cameraData); + emit fieldOfViewChanged(fieldOfView); +} + +qreal QDeclarativeGeoMap::fieldOfView() const +{ + return m_cameraData.fieldOfView(); +} + +void QDeclarativeGeoMap::setMinimumFieldOfView(qreal minimumFieldOfView, bool userSet) +{ + if (minimumFieldOfView > 0 && minimumFieldOfView < 180.0) { + if (userSet) + m_userMinimumFieldOfView = minimumFieldOfView; + qreal oldMinimumFoV = this->minimumFieldOfView(); + + m_minimumFieldOfView = qBound(m_cameraCapabilities.minimumFieldOfView(), + minimumFieldOfView, + m_cameraCapabilities.maximumFieldOfView()); + + if (fieldOfView() < m_minimumFieldOfView) + setFieldOfView(m_minimumFieldOfView); + + if (oldMinimumFoV != m_minimumFieldOfView) + emit minimumFieldOfViewChanged(m_minimumFieldOfView); + } +} + +/*! + \qmlproperty bool QtLocation::Map::minimumFieldOfView + + This property holds the minimum valid field of view for the map, in degrees. + + The minimum tilt field of view by the \l plugin used is a lower bound for + this property. + If the \l plugin property is not set or the plugin does not support mapping, this property is \c 1. + + \sa fieldOfView, maximumFieldOfView + + \since Qt Location 5.9 +*/ +qreal QDeclarativeGeoMap::minimumFieldOfView() const +{ + return m_minimumFieldOfView; +} + +void QDeclarativeGeoMap::setMaximumFieldOfView(qreal maximumFieldOfView, bool userSet) +{ + if (maximumFieldOfView > 0 && maximumFieldOfView < 180.0) { + if (userSet) + m_userMaximumFieldOfView = maximumFieldOfView; + qreal oldMaximumFoV = this->maximumFieldOfView(); + + m_maximumFieldOfView = qBound(m_cameraCapabilities.minimumFieldOfView(), + maximumFieldOfView, + m_cameraCapabilities.maximumFieldOfView()); + + if (fieldOfView() > m_maximumFieldOfView) + setFieldOfView(m_maximumFieldOfView); + + if (oldMaximumFoV != m_maximumFieldOfView) + emit maximumFieldOfViewChanged(m_maximumFieldOfView); + } +} + +/*! + \qmlproperty bool QtLocation::Map::maximumFieldOfView + + This property holds the maximum valid field of view for the map, in degrees. + + The minimum tilt field of view by the \l plugin used is an upper bound for + this property. + If the \l plugin property is not set or the plugin does not support mapping, this property is \c 179. + + \sa fieldOfView, minimumFieldOfView + + \since Qt Location 5.9 +*/ +qreal QDeclarativeGeoMap::maximumFieldOfView() const +{ + return m_maximumFieldOfView; +} + +/*! + \qmlproperty bool QtLocation::Map::minimumTilt + + This property holds the minimum valid tilt for the map, in degrees. + + The minimum tilt defined by the \l plugin used is a lower bound for + this property. + If the \l plugin property is not set or the plugin does not support mapping, this property is \c 0. + + \sa tilt, maximumTilt + + \since Qt Location 5.9 +*/ +qreal QDeclarativeGeoMap::minimumTilt() const +{ + return m_minimumTilt; +} + +void QDeclarativeGeoMap::setMaximumTilt(qreal maximumTilt, bool userSet) +{ + if (maximumTilt >= 0) { + if (userSet) + m_userMaximumTilt = maximumTilt; + qreal oldMaximumTilt = this->maximumTilt(); + + m_maximumTilt = qBound(m_cameraCapabilities.minimumTilt(), + maximumTilt, + m_cameraCapabilities.maximumTilt()); + + if (tilt() > m_maximumTilt) + setTilt(m_maximumTilt); + + if (oldMaximumTilt != m_maximumTilt) + emit maximumTiltChanged(m_maximumTilt); + } +} + +/*! + \qmlproperty bool QtLocation::Map::maximumTilt + + This property holds the maximum valid tilt for the map, in degrees. + + The maximum tilt defined by the \l plugin used is an upper bound for + this property. + If the \l plugin property is not set or the plugin does not support mapping, this property is \c 89.5. + + \sa tilt, minimumTilt + + \since Qt Location 5.9 +*/ +qreal QDeclarativeGeoMap::maximumTilt() const +{ + return m_maximumTilt; +} + +/*! + \qmlproperty coordinate QtLocation::Map::center + + This property holds the coordinate which occupies the center of the + mapping viewport. Invalid center coordinates are ignored. + + The default value is an arbitrary valid coordinate. +*/ +void QDeclarativeGeoMap::setCenter(const QGeoCoordinate ¢er) +{ + if (center == m_cameraData.center()) + return; + + if (!center.isValid()) + return; + + if (m_initialized) { + QGeoCoordinate coord(center); + coord.setLatitude(qBound(-m_maximumViewportLatitude, center.latitude(), m_maximumViewportLatitude)); + m_cameraData.setCenter(coord); + m_map->setCameraData(m_cameraData); + } else { + m_cameraData.setCenter(center); + } + + emit centerChanged(m_cameraData.center()); +} + +QGeoCoordinate QDeclarativeGeoMap::center() const +{ + return m_cameraData.center(); +} + + +/*! + \qmlproperty geoshape QtLocation::Map::visibleRegion + + This property holds the region which occupies the viewport of + the map. The camera is positioned in the center of the shape, and + at the largest integral zoom level possible which allows the + whole shape to be visible on the screen. This implies that + reading this property back shortly after having been set the + returned area is equal or larger than the set area. + + Setting this property implicitly changes the \l center and + \l zoomLevel of the map. Any previously set value to those + properties will be overridden. + + This property does not provide any change notifications. + + \since 5.6 +*/ +void QDeclarativeGeoMap::setVisibleRegion(const QGeoShape &shape) +{ + if (shape.boundingGeoRectangle() == visibleRegion()) + return; + + m_visibleRegion = shape.boundingGeoRectangle(); + if (!m_visibleRegion.isValid() + || (m_visibleRegion.bottomRight().latitude() >= 85.0) // rect entirely outside web mercator + || (m_visibleRegion.topLeft().latitude() <= -85.0)) { + // shape invalidated -> nothing to fit anymore + m_visibleRegion = QGeoRectangle(); + m_pendingFitViewport = false; + return; + } + + if (!m_map || !width() || !height()) { + m_pendingFitViewport = true; + return; + } + + fitViewportToGeoShape(); +} + +QGeoShape QDeclarativeGeoMap::visibleRegion() const +{ + if (!m_map || !width() || !height()) + return m_visibleRegion; + + QGeoCoordinate tl = m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(0, 0)); + QGeoCoordinate br = m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(width(), height())); + + return QGeoRectangle(tl, br); +} + +/*! + \qmlproperty bool QtLocation::Map::copyrightsVisible + + This property holds the visibility of the copyrights notice. The notice is usually + displayed in the bottom left corner. By default, this property is set to \c true. + + \note Many map providers require the notice to be visible as part of the terms and conditions. + Please consult the relevant provider documentation before turning this notice off. + + \since 5.7 +*/ +void QDeclarativeGeoMap::setCopyrightsVisible(bool visible) +{ + if (m_copyrightsVisible == visible) + return; + + if (!m_copyrights.isNull()) + m_copyrights->setCopyrightsVisible(visible); + + m_copyrightsVisible = visible; + emit copyrightsVisibleChanged(visible); +} + +bool QDeclarativeGeoMap::copyrightsVisible() const +{ + return m_copyrightsVisible; +} + + + +/*! + \qmlproperty color QtLocation::Map::color + + This property holds the background color of the map element. + + \since 5.6 +*/ +void QDeclarativeGeoMap::setColor(const QColor &color) +{ + if (color != m_color) { + m_color = color; + update(); + emit colorChanged(m_color); + } +} + +QColor QDeclarativeGeoMap::color() const +{ + return m_color; +} + +/*! + \qmlproperty color 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. + Due to the architecture of the \l Map, it's advised to use the signal emitted for this property + in place of \l {QtQml::Component::completed()}{Component.onCompleted}, to make sure that everything behaves as expected. + + \since 5.9 +*/ +bool QDeclarativeGeoMap::mapReady() const +{ + return m_initialized; +} + +// TODO: offer the possibility to specify the margins. +void QDeclarativeGeoMap::fitViewportToGeoShape() +{ + const int margins = 10; + if (!m_map || !m_visibleRegion.isValid() || width() <= margins || height() <= margins) + return; + + QDoubleVector2D topLeftPoint = m_map->geoProjection().geoToMapProjection(m_visibleRegion.topLeft()); + QDoubleVector2D bottomRightPoint = m_map->geoProjection().geoToMapProjection(m_visibleRegion.bottomRight()); + if (bottomRightPoint.x() < topLeftPoint.x()) // crossing the dateline + bottomRightPoint.setX(bottomRightPoint.x() + 1.0); + + // find center of the bounding box + QDoubleVector2D center = (topLeftPoint + bottomRightPoint) * 0.5; + center.setX(center.x() > 1.0 ? center.x() - 1.0 : center.x()); + QGeoCoordinate centerCoordinate = m_map->geoProjection().mapProjectionToGeo(center); + + // position camera to the center of bounding box + setCenter(centerCoordinate); + + // if the shape is empty we just change center position, not zoom + double bboxWidth = (bottomRightPoint.x() - topLeftPoint.x()) * m_map->mapWidth(); + double bboxHeight = (bottomRightPoint.y() - topLeftPoint.y()) * m_map->mapHeight(); + + if (bboxHeight == 0.0 && bboxWidth == 0.0) + return; + + double zoomRatio = qMax(bboxWidth / (width() - margins), + bboxHeight / (height() - margins)); + zoomRatio = std::log(zoomRatio) / std::log(2.0); + double newZoom = qMax<double>(minimumZoomLevel(), zoomLevel() - zoomRatio); + setZoomLevel(newZoom); +} + + +/*! + \qmlproperty list<MapType> QtLocation::Map::supportedMapTypes + + This read-only property holds the set of \l{MapType}{map types} supported by this map. + + \sa activeMapType +*/ +QQmlListProperty<QDeclarativeGeoMapType> QDeclarativeGeoMap::supportedMapTypes() +{ + return QQmlListProperty<QDeclarativeGeoMapType>(this, m_supportedMapTypes); +} + +/*! + \qmlmethod coordinate QtLocation::Map::toCoordinate(QPointF position, bool clipToViewPort) + + Returns the coordinate which corresponds to the \a position relative to the map item. + + If \a cliptoViewPort is \c true, or not supplied then returns an invalid coordinate if + \a position is not within the current viewport. +*/ +QGeoCoordinate QDeclarativeGeoMap::toCoordinate(const QPointF &position, bool clipToViewPort) const +{ + if (m_map) + return m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(position), clipToViewPort); + else + return QGeoCoordinate(); +} + +/*! + \qmlmethod point QtLocation::Map::fromCoordinate(coordinate coordinate, bool clipToViewPort) + + Returns the position relative to the map item which corresponds to the \a coordinate. + + If \a cliptoViewPort is \c true, or not supplied then returns an invalid QPointF if + \a coordinate is not within the current viewport. +*/ +QPointF QDeclarativeGeoMap::fromCoordinate(const QGeoCoordinate &coordinate, bool clipToViewPort) const +{ + if (m_map) + return m_map->geoProjection().coordinateToItemPosition(coordinate, clipToViewPort).toPointF(); + else + return QPointF(qQNaN(), qQNaN()); +} + +/*! + \qmlmethod void QtLocation::Map::pan(int dx, int dy) + + Starts panning the map by \a dx pixels along the x-axis and + by \a dy pixels along the y-axis. + + Positive values for \a dx move the map right, negative values left. + Positive values for \a dy move the map down, negative values up. + + During panning the \l center, and \l zoomLevel may change. +*/ +void QDeclarativeGeoMap::pan(int dx, int dy) +{ + if (!m_map) + return; + if (dx == 0 && dy == 0) + return; + + QGeoCoordinate coord = m_map->geoProjection().itemPositionToCoordinate( + QDoubleVector2D(m_map->viewportWidth() / 2 + dx, + m_map->viewportHeight() / 2 + dy)); + setCenter(coord); +} + + +/*! + \qmlmethod void QtLocation::Map::prefetchData() + + Optional hint that allows the map to prefetch during this idle period +*/ +void QDeclarativeGeoMap::prefetchData() +{ + if (!m_map) + return; + m_map->prefetchData(); +} + +/*! + \qmlmethod void QtLocation::Map::clearData() + + Clears map data collected by the currently selected plugin. + \note This method will delete cached files. + \sa plugin +*/ +void QDeclarativeGeoMap::clearData() +{ + m_map->clearData(); +} + +/*! + \qmlproperty string QtLocation::Map::errorString + + This read-only property holds the textual presentation of the latest mapping provider error. + If no error has occurred, an empty string is returned. + + An empty string may also be returned if an error occurred which has no associated + textual representation. + + \sa QGeoServiceProvider::errorString() +*/ + +QString QDeclarativeGeoMap::errorString() const +{ + return m_errorString; +} + +/*! + \qmlproperty enumeration QtLocation::Map::error + + This read-only property holds the last occurred mapping service provider error. + + \list + \li Map.NoError - No error has occurred. + \li Map.NotSupportedError -The maps plugin property was not set or there is no mapping manager associated with the plugin. + \li Map.UnknownParameterError -The plugin did not recognize one of the parameters it was given. + \li Map.MissingRequiredParameterError - The plugin did not find one of the parameters it was expecting. + \li Map.ConnectionError - The plugin could not connect to its backend service or database. + \endlist + + \sa QGeoServiceProvider::Error +*/ + +QGeoServiceProvider::Error QDeclarativeGeoMap::error() const +{ + return m_error; +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::touchEvent(QTouchEvent *event) +{ + if (isInteractive()) { + m_gestureArea->handleTouchEvent(event); + } else { + //ignore event so sythesized event is generated; + QQuickItem::touchEvent(event); + } +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::wheelEvent(QWheelEvent *event) +{ + if (isInteractive()) + m_gestureArea->handleWheelEvent(event); + else + QQuickItem::wheelEvent(event); + +} + +bool QDeclarativeGeoMap::isInteractive() +{ + return (m_gestureArea->enabled() && m_gestureArea->acceptedGestures()) || m_gestureArea->isActive(); +} + +/*! + \internal +*/ +bool QDeclarativeGeoMap::childMouseEventFilter(QQuickItem *item, QEvent *event) +{ + Q_UNUSED(item) + if (!isVisible() || !isEnabled() || !isInteractive()) + return QQuickItem::childMouseEventFilter(item, event); + + switch (event->type()) { + case QEvent::MouseButtonPress: + case QEvent::MouseMove: + case QEvent::MouseButtonRelease: + return sendMouseEvent(static_cast<QMouseEvent *>(event)); + case QEvent::UngrabMouse: { + QQuickWindow *win = window(); + if (!win) break; + if (!win->mouseGrabberItem() || + (win->mouseGrabberItem() && + win->mouseGrabberItem() != this)) { + // child lost grab, we could even lost + // some events if grab already belongs for example + // in item in diffrent window , clear up states + mouseUngrabEvent(); + } + break; + } + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + case QEvent::TouchCancel: + if (static_cast<QTouchEvent *>(event)->touchPoints().count() >= 2) { + // 1 touch point = handle with MouseEvent (event is always synthesized) + // let the synthesized mouse event grab the mouse, + // note there is no mouse grabber at this point since + // touch event comes first (see Qt::AA_SynthesizeMouseForUnhandledTouchEvents) + return sendTouchEvent(static_cast<QTouchEvent *>(event)); + } + default: + break; + } + return QQuickItem::childMouseEventFilter(item, event); +} + +/*! + \qmlmethod void QtLocation::Map::addMapItem(MapItem item) + + Adds the given \a item to the Map (for example MapQuickItem, MapCircle). If the object + already is on the Map, it will not be added again. + + As an example, consider the case where you have a MapCircle representing your current position: + + \snippet declarative/maps.qml QtQuick import + \snippet declarative/maps.qml QtLocation import + \codeline + \snippet declarative/maps.qml Map addMapItem MapCircle at current position + + \note MapItemViews cannot be added with this method. + + \sa mapItems, removeMapItem, clearMapItems +*/ + +void QDeclarativeGeoMap::addMapItem(QDeclarativeGeoMapItemBase *item) +{ + if (!item || item->quickMap()) + return; + // If the item comes from a MapItemGroup, do not reparent it. + if (!qobject_cast<QDeclarativeGeoMapItemGroup *>(item->parentItem())) + item->setParentItem(this); + m_mapItems.append(item); + if (m_map) { + item->setMap(this, m_map); + m_map->addMapItem(item); + } + emit mapItemsChanged(); +} + +/*! + \qmlmethod void QtLocation::Map::addMapParameter(MapParameter parameter) + + Adds a MapParameter object to the map. The effect of this call is dependent + on the combination of the content of the MapParameter and the type of + underlying QGeoMap. If a MapParameter that is not supported by the underlying + QGeoMap gets added, the call has no effect. + + The release of this API with Qt 5.9 is a Technology Preview. + + \sa MapParameter, removeMapParameter, mapParameters, clearMapParameters + + \since 5.9 +*/ +void QDeclarativeGeoMap::addMapParameter(QDeclarativeGeoMapParameter *parameter) +{ + if (!parameter->isComponentComplete()) { + connect(parameter, &QDeclarativeGeoMapParameter::completed, this, &QDeclarativeGeoMap::addMapParameter); + return; + } + + disconnect(parameter); + if (m_mapParameters.contains(parameter)) + return; + parameter->setParent(this); + m_mapParameters.append(parameter); // parameter now owned by QDeclarativeGeoMap + if (m_map) + m_map->addParameter(parameter); +} + +/*! + \qmlmethod void QtLocation::Map::removeMapParameter(MapParameter parameter) + + Removes the given MapParameter object from the map. + + The release of this API with Qt 5.9 is a Technology Preview. + + \sa MapParameter, addMapParameter, mapParameters, clearMapParameters + + \since 5.9 +*/ +void QDeclarativeGeoMap::removeMapParameter(QDeclarativeGeoMapParameter *parameter) +{ + if (!m_mapParameters.contains(parameter)) + return; + if (m_map) + m_map->removeParameter(parameter); + m_mapParameters.removeOne(parameter); +} + +/*! + \qmlmethod void QtLocation::Map::clearMapParameters() + + Removes all map parameters from the map. + + The release of this API with Qt 5.9 is a Technology Preview. + + \sa MapParameter, mapParameters, addMapParameter, removeMapParameter, clearMapParameters + + \since 5.9 +*/ +void QDeclarativeGeoMap::clearMapParameters() +{ + if (m_map) + m_map->clearParameters(); + m_mapParameters.clear(); +} + +/*! + \qmlproperty list<MapParameters> QtLocation::Map::mapParameters + + Returns the list of all map parameters in no particular order. + These items include map parameters that were declared statically as part of + the type declaration, as well as dynamical map parameters (\l addMapParameter). + + The release of this API with Qt 5.9 is a Technology Preview. + + \sa MapParameter, addMapParameter, removeMapParameter, clearMapParameters + + \since 5.9 +*/ +QList<QObject *> QDeclarativeGeoMap::mapParameters() +{ + QList<QObject *> ret; + for (QDeclarativeGeoMapParameter *p : qAsConst(m_mapParameters)) + ret << p; + return ret; +} + +/*! + \qmlproperty list<MapItem> QtLocation::Map::mapItems + + Returns the list of all map items in no particular order. + These items include items that were declared statically as part of + the type declaration, as well as dynamical items (\l addMapItem, + \l MapItemView). + + \sa addMapItem, removeMapItem, clearMapItems +*/ + +QList<QObject *> QDeclarativeGeoMap::mapItems() +{ + QList<QObject *> ret; + foreach (const QPointer<QDeclarativeGeoMapItemBase> &ptr, m_mapItems) { + if (ptr) + ret << ptr.data(); + } + return ret; +} + +/*! + \qmlmethod void QtLocation::Map::removeMapItem(MapItem item) + + Removes the given \a item from the Map (for example MapQuickItem, MapCircle). If + the MapItem does not exist or was not previously added to the map, the + method does nothing. + + \sa mapItems, addMapItem, clearMapItems +*/ +void QDeclarativeGeoMap::removeMapItem(QDeclarativeGeoMapItemBase *ptr) +{ + if (!ptr || !m_map) + return; + m_map->removeMapItem(ptr); + QPointer<QDeclarativeGeoMapItemBase> item(ptr); + if (!m_mapItems.contains(item)) + return; + if (item->parentItem() == this) + item->setParentItem(0); + item->setMap(0, 0); + // these can be optimized for perf, as we already check the 'contains' above + m_mapItems.removeOne(item); + emit mapItemsChanged(); +} + +/*! + \qmlmethod void QtLocation::Map::clearMapItems() + + Removes all items and item groups from the map. + + \sa mapItems, addMapItem, removeMapItem, addMapItemGroup, removeMapItemGroup +*/ +void QDeclarativeGeoMap::clearMapItems() +{ + m_map->clearMapItems(); + if (m_mapItems.isEmpty()) + return; + for (auto i : qAsConst(m_mapItems)) { + if (i) { + i->setMap(0, 0); + if (i->parentItem() == this) + i->setParentItem(0); + } + } + m_mapItems.clear(); + m_mapItemGroups.clear(); + emit mapItemsChanged(); +} + +/*! + \qmlmethod void QtLocation::Map::addMapItemGroup(MapItemGroup itemGroup) + + Adds the map items contained in the given \a itemGroup to the Map + (for example MapQuickItem, MapCircle). These items will be reparented, and the map + will be their new parent. Property bindings defined using \e{parent.} inside a MapItemGroup + will therefore not work. + + \sa MapItemGroup, removeMapItemGroup + + \since 5.9 +*/ +void QDeclarativeGeoMap::addMapItemGroup(QDeclarativeGeoMapItemGroup *itemGroup) +{ + if (!itemGroup) + return; + + QPointer<QDeclarativeGeoMapItemGroup> g(itemGroup); + if (m_mapItemGroups.contains(g)) + return; + + m_mapItemGroups.append(g); + const QList<QQuickItem *> quickKids = g->childItems(); + for (auto c: quickKids) { + QDeclarativeGeoMapItemBase *mapItem = qobject_cast<QDeclarativeGeoMapItemBase *>(c); + if (mapItem) + addMapItem(mapItem); + } + itemGroup->setParentItem(this); +} + +/*! + \qmlmethod void QtLocation::Map::removeMapItemGroup(MapItemGroup itemGroup) + + Removes \a itemGroup and the items contained therein from the Map. + + \sa MapItemGroup, addMapItemGroup + + \since 5.9 +*/ +void QDeclarativeGeoMap::removeMapItemGroup(QDeclarativeGeoMapItemGroup *itemGroup) +{ + if (!itemGroup) + return; + + QPointer<QDeclarativeGeoMapItemGroup> g(itemGroup); + if (!m_mapItemGroups.removeOne(g)) + return; + + const QList<QQuickItem *> quickKids = itemGroup->childItems(); + for (auto c: quickKids) { + QDeclarativeGeoMapItemBase *mapItem = qobject_cast<QDeclarativeGeoMapItemBase *>(c); + if (mapItem) + removeMapItem(mapItem); + } + itemGroup->setParentItem(0); +} + +/*! + \qmlproperty MapType QtLocation::Map::activeMapType + + \brief Access to the currently active \l{MapType}{map type}. + + This property can be set to change the active \l{MapType}{map type}. + See the \l{Map::supportedMapTypes}{supportedMapTypes} property for possible values. + + \sa MapType +*/ +void QDeclarativeGeoMap::setActiveMapType(QDeclarativeGeoMapType *mapType) +{ + if (m_activeMapType->mapType() != mapType->mapType()) { + m_activeMapType = mapType; + if (m_map) + m_map->setActiveMapType(mapType->mapType()); + emit activeMapTypeChanged(); + } +} + +QDeclarativeGeoMapType * QDeclarativeGeoMap::activeMapType() const +{ + return m_activeMapType; +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + m_gestureArea->setSize(newGeometry.size()); + QQuickItem::geometryChanged(newGeometry, oldGeometry); + + if (!m_map || !newGeometry.size().isValid()) + return; + + m_map->setViewportSize(newGeometry.size().toSize()); + + if (!m_initialized) { + initialize(); + } else { + setMinimumZoomLevel(m_map->minimumZoom(), false); + + // Update the center latitudinal threshold + double maximumCenterLatitudeAtZoom = m_map->maximumCenterLatitudeAtZoom(m_cameraData); + if (maximumCenterLatitudeAtZoom != m_maximumViewportLatitude) { + m_maximumViewportLatitude = maximumCenterLatitudeAtZoom; + QGeoCoordinate coord = m_cameraData.center(); + coord.setLatitude(qBound(-m_maximumViewportLatitude, coord.latitude(), m_maximumViewportLatitude)); + + if (coord != m_cameraData.center()) { + m_cameraData.setCenter(coord); + m_map->setCameraData(m_cameraData); + emit centerChanged(m_cameraData.center()); + } + } + } + + /*! + The fitViewportTo*() functions depend on a valid map geometry. + If they were called prior to the first resize they cause + the zoomlevel to jump to 0 (showing the world). Therefore the + calls were queued up until now. + + Multiple fitViewportTo*() calls replace each other. + */ + if (m_pendingFitViewport && width() && height()) { + fitViewportToGeoShape(); + m_pendingFitViewport = false; + } + +} + +/*! + \qmlmethod void QtLocation::Map::fitViewportToMapItems() + + Fits the current viewport to the boundary of all map items. The camera is positioned + in the center of the map items, and at the largest integral zoom level possible which + allows all map items to be visible on screen. + + \sa fitViewportToVisibleMapItems +*/ +void QDeclarativeGeoMap::fitViewportToMapItems() +{ + fitViewportToMapItemsRefine(true, false); +} + +/*! + \qmlmethod void QtLocation::Map::fitViewportToVisibleMapItems() + + Fits the current viewport to the boundary of all \b visible map items. + The camera is positioned in the center of the map items, and at the largest integral + zoom level possible which allows all map items to be visible on screen. + + \sa fitViewportToMapItems +*/ +void QDeclarativeGeoMap::fitViewportToVisibleMapItems() +{ + fitViewportToMapItemsRefine(true, true); +} + +/*! + \internal +*/ +void QDeclarativeGeoMap::fitViewportToMapItemsRefine(bool refine, bool onlyVisible) +{ + if (!m_map) + return; + + if (m_mapItems.size() == 0) + return; + + double minX = 0; + double maxX = 0; + double minY = 0; + double maxY = 0; + double topLeftX = 0; + double topLeftY = 0; + double bottomRightX = 0; + double bottomRightY = 0; + bool haveQuickItem = false; + + // find bounds of all map items + int itemCount = 0; + for (int i = 0; i < m_mapItems.count(); ++i) { + if (!m_mapItems.at(i)) + continue; + QDeclarativeGeoMapItemBase *item = m_mapItems.at(i).data(); + if (!item || (onlyVisible && (!item->isVisible() || item->mapItemOpacity() <= 0.0))) + continue; + + // skip quick items in the first pass and refine the fit later + if (refine) { + QDeclarativeGeoMapQuickItem *quickItem = + qobject_cast<QDeclarativeGeoMapQuickItem*>(item); + if (quickItem) { + haveQuickItem = true; + continue; + } + } + // Force map items to update immediately. Needed to ensure correct item size and positions + // when recursively calling this function. + // TODO: See if we really need updatePolish on delegated items, in particular + // in relation to + // a) fitViewportToMapItems + // b) presence of MouseArea + if (item->isPolishScheduled()) + item->updatePolish(); + + topLeftX = item->position().x(); + topLeftY = item->position().y(); + bottomRightX = topLeftX + item->width(); + bottomRightY = topLeftY + item->height(); + + if (itemCount == 0) { + minX = topLeftX; + maxX = bottomRightX; + minY = topLeftY; + maxY = bottomRightY; + } else { + minX = qMin(minX, topLeftX); + maxX = qMax(maxX, bottomRightX); + minY = qMin(minY, topLeftY); + maxY = qMax(maxY, bottomRightY); + } + ++itemCount; + } + + if (itemCount == 0) { + if (haveQuickItem) + fitViewportToMapItemsRefine(false, onlyVisible); + return; + } + double bboxWidth = maxX - minX; + double bboxHeight = maxY - minY; + double bboxCenterX = minX + (bboxWidth / 2.0); + double bboxCenterY = minY + (bboxHeight / 2.0); + + // position camera to the center of bounding box + QGeoCoordinate coordinate; + coordinate = m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(bboxCenterX, bboxCenterY), false); + setProperty("center", QVariant::fromValue(coordinate)); + + // adjust zoom + double bboxWidthRatio = bboxWidth / (bboxWidth + bboxHeight); + double mapWidthRatio = width() / (width() + height()); + double zoomRatio; + + if (bboxWidthRatio > mapWidthRatio) + zoomRatio = bboxWidth / width(); + else + zoomRatio = bboxHeight / height(); + + qreal newZoom = std::log10(zoomRatio) / std::log10(0.5); + newZoom = std::floor(qMax(minimumZoomLevel(), (zoomLevel() + newZoom))); + setProperty("zoomLevel", QVariant::fromValue(newZoom)); + + // as map quick items retain the same screen size after the camera zooms in/out + // we refine the viewport again to achieve better results + if (refine) + fitViewportToMapItemsRefine(false, onlyVisible); +} + +bool QDeclarativeGeoMap::sendMouseEvent(QMouseEvent *event) +{ + QPointF localPos = mapFromScene(event->windowPos()); + QQuickWindow *win = window(); + QQuickItem *grabber = win ? win->mouseGrabberItem() : 0; + bool stealEvent = m_gestureArea->isActive(); + + if ((stealEvent || contains(localPos)) && (!grabber || (!grabber->keepMouseGrab() && !grabber->keepTouchGrab()))) { + QScopedPointer<QMouseEvent> mouseEvent(QQuickWindowPrivate::cloneMouseEvent(event, &localPos)); + mouseEvent->setAccepted(false); + + switch (mouseEvent->type()) { + case QEvent::MouseMove: + m_gestureArea->handleMouseMoveEvent(mouseEvent.data()); + break; + case QEvent::MouseButtonPress: + m_gestureArea->handleMousePressEvent(mouseEvent.data()); + break; + case QEvent::MouseButtonRelease: + m_gestureArea->handleMouseReleaseEvent(mouseEvent.data()); + break; + default: + break; + } + + stealEvent = m_gestureArea->isActive(); + grabber = win ? win->mouseGrabberItem() : 0; + + if (grabber && stealEvent && !grabber->keepMouseGrab() && !grabber->keepTouchGrab() && grabber != this) + grabMouse(); + + if (stealEvent) { + //do not deliver + event->setAccepted(true); + return true; + } else { + return false; + } + } + + return false; +} + +bool QDeclarativeGeoMap::sendTouchEvent(QTouchEvent *event) +{ + const QQuickPointerDevice *touchDevice = QQuickPointerDevice::touchDevice(event->device()); + const QTouchEvent::TouchPoint &point = event->touchPoints().first(); + + auto touchPointGrabberItem = [touchDevice](const QTouchEvent::TouchPoint &point) -> QQuickItem* { + if (QQuickEventPoint *eventPointer = touchDevice->pointerEvent()->pointById(point.id())) + return eventPointer->grabber(); + return nullptr; + }; + + QQuickItem *grabber = touchPointGrabberItem(point); + + bool stealEvent = m_gestureArea->isActive(); + bool containsPoint = contains(mapFromScene(point.scenePos())); + + if ((stealEvent || containsPoint) && (!grabber || !grabber->keepTouchGrab())) { + QScopedPointer<QTouchEvent> touchEvent(new QTouchEvent(event->type(), event->device(), event->modifiers(), event->touchPointStates(), event->touchPoints())); + touchEvent->setTimestamp(event->timestamp()); + touchEvent->setAccepted(false); + + m_gestureArea->handleTouchEvent(touchEvent.data()); + stealEvent = m_gestureArea->isActive(); + grabber = touchPointGrabberItem(point); + + if (grabber && stealEvent && !grabber->keepTouchGrab() && grabber != this) { + QVector<int> ids; + foreach (const QTouchEvent::TouchPoint &tp, event->touchPoints()) { + if (!(tp.state() & Qt::TouchPointReleased)) { + ids.append(tp.id()); + } + } + grabTouchPoints(ids); + } + + if (stealEvent) { + //do not deliver + event->setAccepted(true); + return true; + } else { + return false; + } + } + + return false; +} + +QT_END_NAMESPACE diff --git a/src/location/declarativemaps/qdeclarativegeomap_p.h b/src/location/declarativemaps/qdeclarativegeomap_p.h new file mode 100644 index 00000000..f08998e2 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeomap_p.h @@ -0,0 +1,292 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEGEOMAP_H +#define QDECLARATIVEGEOMAP_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtLocation/private/qlocationglobal_p.h> +#include <QtLocation/private/qdeclarativegeomapitemview_p.h> +#include <QtLocation/private/qquickgeomapgesturearea_p.h> +#include <QtLocation/private/qdeclarativegeomapitemgroup_p.h> +#include <QtLocation/qgeoserviceprovider.h> +#include <QtLocation/private/qgeocameradata_p.h> +#include <QtLocation/private/qgeocameracapabilities_p.h> +#include <QtQuick/QQuickItem> +#include <QtCore/QList> +#include <QtCore/QPointer> +#include <QtGui/QColor> +#include <QtPositioning/qgeoshape.h> +#include <QtLocation/private/qgeomap_p.h> + +QT_BEGIN_NAMESPACE + +class QDeclarativeGeoServiceProvider; +class QDeclarativeGeoMapType; +class QDeclarativeGeoMapCopyrightNotice; +class QDeclarativeGeoMapParameter; + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativeGeoMap : public QQuickItem +{ + Q_OBJECT + Q_ENUMS(QGeoServiceProvider::Error) + Q_PROPERTY(QQuickGeoMapGestureArea *gesture READ gesture CONSTANT) + Q_PROPERTY(QDeclarativeGeoServiceProvider *plugin READ plugin WRITE setPlugin NOTIFY pluginChanged) + Q_PROPERTY(qreal minimumZoomLevel READ minimumZoomLevel WRITE setMinimumZoomLevel NOTIFY minimumZoomLevelChanged) + Q_PROPERTY(qreal maximumZoomLevel READ maximumZoomLevel WRITE setMaximumZoomLevel NOTIFY maximumZoomLevelChanged) + Q_PROPERTY(qreal zoomLevel READ zoomLevel WRITE setZoomLevel NOTIFY zoomLevelChanged) + + Q_PROPERTY(qreal tilt READ tilt WRITE setTilt NOTIFY tiltChanged) + Q_PROPERTY(qreal minimumTilt READ minimumTilt WRITE setMinimumTilt NOTIFY minimumTiltChanged) + Q_PROPERTY(qreal maximumTilt READ maximumTilt WRITE setMaximumTilt NOTIFY maximumTiltChanged) + + Q_PROPERTY(qreal bearing READ bearing WRITE setBearing NOTIFY bearingChanged) + + Q_PROPERTY(qreal fieldOfView READ fieldOfView WRITE setFieldOfView NOTIFY fieldOfViewChanged) + Q_PROPERTY(qreal minimumFieldOfView READ minimumFieldOfView WRITE setMinimumFieldOfView NOTIFY minimumFieldOfViewChanged) + Q_PROPERTY(qreal maximumFieldOfView READ maximumFieldOfView WRITE setMaximumFieldOfView NOTIFY minimumFieldOfViewChanged) + + Q_PROPERTY(QDeclarativeGeoMapType *activeMapType READ activeMapType WRITE setActiveMapType NOTIFY activeMapTypeChanged) + Q_PROPERTY(QQmlListProperty<QDeclarativeGeoMapType> supportedMapTypes READ supportedMapTypes NOTIFY supportedMapTypesChanged) + Q_PROPERTY(QGeoCoordinate center READ center WRITE setCenter NOTIFY centerChanged) + Q_PROPERTY(QList<QObject *> mapItems READ mapItems NOTIFY mapItemsChanged) + Q_PROPERTY(QList<QObject *> mapParameters READ mapParameters) + Q_PROPERTY(QGeoServiceProvider::Error error READ error NOTIFY errorChanged) + Q_PROPERTY(QString errorString READ errorString NOTIFY errorChanged) + Q_PROPERTY(QGeoShape visibleRegion READ visibleRegion WRITE setVisibleRegion) + Q_PROPERTY(bool copyrightsVisible READ copyrightsVisible WRITE setCopyrightsVisible NOTIFY copyrightsVisibleChanged) + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) + Q_PROPERTY(bool mapReady READ mapReady NOTIFY mapReadyChanged) + Q_INTERFACES(QQmlParserStatus) + +public: + + explicit QDeclarativeGeoMap(QQuickItem *parent = 0); + ~QDeclarativeGeoMap(); + + void setPlugin(QDeclarativeGeoServiceProvider *plugin); + QDeclarativeGeoServiceProvider *plugin() const; + + void setActiveMapType(QDeclarativeGeoMapType *mapType); + QDeclarativeGeoMapType *activeMapType() const; + + void setMinimumZoomLevel(qreal minimumZoomLevel, bool userSet = true); + qreal minimumZoomLevel() const; + + void setMaximumZoomLevel(qreal maximumZoomLevel, bool userSet = true); + qreal maximumZoomLevel() const; + + void setZoomLevel(qreal zoomLevel); + qreal zoomLevel() const; + + void setBearing(qreal bearing); + qreal bearing() const; + + void setTilt(qreal tilt); + qreal tilt() const; + void setMinimumTilt(qreal minimumTilt, bool userSet = true); + qreal minimumTilt() const; + void setMaximumTilt(qreal maximumTilt, bool userSet = true); + qreal maximumTilt() const; + + void setFieldOfView(qreal fieldOfView); + qreal fieldOfView() const; + void setMinimumFieldOfView(qreal minimumFieldOfView, bool userSet = true); + qreal minimumFieldOfView() const; + void setMaximumFieldOfView(qreal maximumFieldOfView, bool userSet = true); + qreal maximumFieldOfView() const; + + void setCenter(const QGeoCoordinate ¢er); + QGeoCoordinate center() const; + + void setVisibleRegion(const QGeoShape &shape); + QGeoShape visibleRegion() const; + + void setCopyrightsVisible(bool visible); + bool copyrightsVisible() const; + + void setColor(const QColor &color); + QColor color() const; + + bool mapReady() const; + + QQmlListProperty<QDeclarativeGeoMapType> supportedMapTypes(); + + Q_INVOKABLE void removeMapItem(QDeclarativeGeoMapItemBase *item); + Q_INVOKABLE void addMapItem(QDeclarativeGeoMapItemBase *item); + + Q_INVOKABLE void addMapItemGroup(QDeclarativeGeoMapItemGroup *itemGroup); + Q_INVOKABLE void removeMapItemGroup(QDeclarativeGeoMapItemGroup *itemGroup); + + Q_INVOKABLE void clearMapItems(); + QList<QObject *> mapItems(); + + Q_INVOKABLE void addMapParameter(QDeclarativeGeoMapParameter *parameter); + Q_INVOKABLE void removeMapParameter(QDeclarativeGeoMapParameter *parameter); + Q_INVOKABLE void clearMapParameters(); + QList<QObject *> mapParameters(); + + Q_INVOKABLE QGeoCoordinate toCoordinate(const QPointF &position, bool clipToViewPort = true) const; + Q_INVOKABLE QPointF fromCoordinate(const QGeoCoordinate &coordinate, bool clipToViewPort = true) const; + + QQuickGeoMapGestureArea *gesture(); + + Q_INVOKABLE void fitViewportToMapItems(); + Q_INVOKABLE void fitViewportToVisibleMapItems(); + Q_INVOKABLE void pan(int dx, int dy); + Q_INVOKABLE void prefetchData(); // optional hint for prefetch + Q_INVOKABLE void clearData(); + + QString errorString() const; + QGeoServiceProvider::Error error() const; + +Q_SIGNALS: + void pluginChanged(QDeclarativeGeoServiceProvider *plugin); + void zoomLevelChanged(qreal zoomLevel); + void centerChanged(const QGeoCoordinate &coordinate); + void activeMapTypeChanged(); + void supportedMapTypesChanged(); + void minimumZoomLevelChanged(); + void maximumZoomLevelChanged(); + void mapItemsChanged(); + void errorChanged(); + void copyrightLinkActivated(const QString &link); + void copyrightsVisibleChanged(bool visible); + void colorChanged(const QColor &color); + void bearingChanged(qreal bearing); + void tiltChanged(qreal tilt); + void fieldOfViewChanged(qreal fieldOfView); + void minimumTiltChanged(qreal minimumTilt); + void maximumTiltChanged(qreal maximumTilt); + void minimumFieldOfViewChanged(qreal minimumFieldOfView); + void maximumFieldOfViewChanged(qreal maximumFieldOfView); + void copyrightsChanged(const QImage ©rightsImage); + void copyrightsChanged(const QString ©rightsHtml); + void mapReadyChanged(bool ready); + +protected: + void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE ; + void mouseMoveEvent(QMouseEvent *event) Q_DECL_OVERRIDE ; + void mouseReleaseEvent(QMouseEvent *event) Q_DECL_OVERRIDE ; + void mouseUngrabEvent() Q_DECL_OVERRIDE ; + void touchUngrabEvent() Q_DECL_OVERRIDE; + void touchEvent(QTouchEvent *event) Q_DECL_OVERRIDE ; + void wheelEvent(QWheelEvent *event) Q_DECL_OVERRIDE ; + + bool childMouseEventFilter(QQuickItem *item, QEvent *event) Q_DECL_OVERRIDE; + bool sendMouseEvent(QMouseEvent *event); + bool sendTouchEvent(QTouchEvent *event); + + void componentComplete() Q_DECL_OVERRIDE; + QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *) Q_DECL_OVERRIDE; + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) Q_DECL_OVERRIDE; + + void setError(QGeoServiceProvider::Error error, const QString &errorString); + void initialize(); +private Q_SLOTS: + void mappingManagerInitialized(); + void pluginReady(); + void onMapChildrenChanged(); + void onSupportedMapTypesChanged(); + void onCameraCapabilitiesChanged(const QGeoCameraCapabilities &oldCameraCapabilities); + +private: + void setupMapView(QDeclarativeGeoMapItemView *view); + void populateMap(); + void populateParameters(); + void fitViewportToMapItemsRefine(bool refine, bool onlyVisible); + void fitViewportToGeoShape(); + bool isInteractive(); + +private: + QDeclarativeGeoServiceProvider *m_plugin; + QGeoMappingManager *m_mappingManager; + QDeclarativeGeoMapType *m_activeMapType; + QList<QDeclarativeGeoMapType *> m_supportedMapTypes; + QList<QDeclarativeGeoMapItemView *> m_mapViews; + QQuickGeoMapGestureArea *m_gestureArea; + QPointer<QGeoMap> m_map; + QPointer<QDeclarativeGeoMapCopyrightNotice> m_copyrights; + QList<QPointer<QDeclarativeGeoMapItemBase> > m_mapItems; + QList<QPointer<QDeclarativeGeoMapItemGroup> > m_mapItemGroups; + QString m_errorString; + QGeoServiceProvider::Error m_error; + QGeoRectangle m_visibleRegion; + QColor m_color; + QGeoCameraData m_cameraData; + bool m_componentCompleted; + bool m_pendingFitViewport; + bool m_copyrightsVisible; + double m_maximumViewportLatitude; + bool m_initialized; + QList<QDeclarativeGeoMapParameter *> m_mapParameters; + QGeoCameraCapabilities m_cameraCapabilities; + qreal m_userMinimumZoomLevel; + qreal m_userMaximumZoomLevel; + + qreal m_minimumTilt; + qreal m_maximumTilt; + qreal m_userMinimumTilt; + qreal m_userMaximumTilt; + + qreal m_minimumFieldOfView; + qreal m_maximumFieldOfView; + qreal m_userMinimumFieldOfView; + qreal m_userMaximumFieldOfView; + + friend class QDeclarativeGeoMapItem; + friend class QDeclarativeGeoMapItemView; + friend class QQuickGeoMapGestureArea; + friend class QDeclarativeGeoMapCopyrightNotice; + Q_DISABLE_COPY(QDeclarativeGeoMap) +}; + + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativeGeoMap) + +#endif diff --git a/src/location/declarativemaps/qdeclarativegeomapcopyrightsnotice.cpp b/src/location/declarativemaps/qdeclarativegeomapcopyrightsnotice.cpp new file mode 100644 index 00000000..fdfb645a --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeomapcopyrightsnotice.cpp @@ -0,0 +1,316 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Aaron McCarthy <mccarthy.aaron@gmail.com> +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativegeomapcopyrightsnotice_p.h" + +#include <QtGui/QTextDocument> +#include <QtGui/QAbstractTextDocumentLayout> +#include <QtGui/QPainter> +#include <QtQuick/private/qquickanchors_p.h> +#include <QtQuick/private/qquickanchors_p_p.h> +#include <QtLocation/private/qdeclarativegeomap_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype MapCopyrightNotice + \instantiates QDeclarativeGeoMapCopyrightNotice + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-maps + \since Qt Location 5.9 + + \brief The MapCopyrightNotice item displays the current valid + copyright notice for a Map element. + + This object can be used to place an additional copyright notices + programmatically. + + Note that declaring a MapCopyrightNotice inside a QtLocation::Map element + is not possible, like for any other QQuickItem. + + The release of this API with Qt 5.9 is a Technology Preview. +*/ + +/*! + \qmlproperty Map QtLocation::MapCopyrightNotice::mapSource + + This property holds the current map source providing the copyright data shown + in this notice. + In order to let the MapCopyrightNotice display a copyright, this property must + be set, as it is the only data source for this element. +*/ + +/*! + \qmlproperty string QtLocation::MapCopyrightNotice::styleSheet + + This property holds the current css2.1 style sheet used to style the copyright notice, if in HTML form. + + Example: + \code + MapCopyrightNotice { + mapSource: myMap + styleSheet: "body { color : green; font-family: \"Lucida\"; font-size: 8px} a{ font-size: 8px; color:#A62900}" + } + \endcode +*/ + +QDeclarativeGeoMapCopyrightNotice::QDeclarativeGeoMapCopyrightNotice(QQuickItem *parent) +: QQuickPaintedItem(parent), m_copyrightsHtml(0), m_copyrightsVisible(true), m_mapSource(0), + m_userDefinedStyleSheet(false) +{ + // If this item is constructed inside the map, automatically anchor it where it always used to be. + if (qobject_cast<QDeclarativeGeoMap *>(parent)) + anchorToBottomLeft(); +} + +QDeclarativeGeoMapCopyrightNotice::~QDeclarativeGeoMapCopyrightNotice() +{ +} + +void QDeclarativeGeoMapCopyrightNotice::anchorToBottomLeft() +{ + if (!parent()) + return; + QQuickAnchors *anchors = property("anchors").value<QQuickAnchors *>(); + if (anchors) { + anchors->setLeft(QQuickAnchorLine(qobject_cast<QQuickItem *>(parent()), QQuickAnchors::LeftAnchor)); + anchors->setBottom(QQuickAnchorLine(qobject_cast<QQuickItem *>(parent()), QQuickAnchors::BottomAnchor)); + } +} + +void QDeclarativeGeoMapCopyrightNotice::setMapSource(QDeclarativeGeoMap *mapSource) +{ + if (m_mapSource == mapSource) + return; + + if (m_mapSource) { + // disconnect this object from current map source + m_mapSource->disconnect(this); + m_mapSource->m_map->disconnect(this); + m_copyrightsHtml->clear(); + m_copyrightsImage = QImage(); + m_mapSource = Q_NULLPTR; + } + + if (mapSource) { + m_mapSource = mapSource; + // First update the copyright. Only Image will do here, no need to store HTML right away. + if (!mapSource->m_copyrights->m_copyrightsImage.isNull()) + m_copyrightsImage = mapSource->m_copyrights->m_copyrightsImage; + + connect(m_mapSource, SIGNAL(copyrightsChanged(QImage)), + this, SLOT(copyrightsChanged(QImage))); + connect(m_mapSource, SIGNAL(copyrightsChanged(QString)), + this, SLOT(copyrightsChanged(QString))); + connect(m_mapSource->m_map, SIGNAL(copyrightsStyleSheetChanged(QString)), + this, SLOT(onCopyrightsStyleSheetChanged(QString))); + connect(this, SIGNAL(linkActivated(QString)), + m_mapSource, SIGNAL(copyrightLinkActivated(QString))); + + onCopyrightsStyleSheetChanged(m_mapSource->m_map->copyrightsStyleSheet()); + } + + update(); + emit mapSourceChanged(); +} + +QDeclarativeGeoMap *QDeclarativeGeoMapCopyrightNotice::mapSource() +{ + return m_mapSource; +} + +QString QDeclarativeGeoMapCopyrightNotice::styleSheet() const +{ + return m_styleSheet; +} + +void QDeclarativeGeoMapCopyrightNotice::setStyleSheet(const QString &styleSheet) +{ + m_userDefinedStyleSheet = true; + + if (styleSheet == m_styleSheet) + return; + + m_styleSheet = styleSheet; + if (!m_html.isEmpty() && m_copyrightsHtml) { + delete m_copyrightsHtml; + createCopyright(); + m_copyrightsHtml->setHtml(m_html); + } + rasterizeHtmlAndUpdate(); + emit styleSheetChanged(m_styleSheet); +} + +/*! + \internal +*/ +void QDeclarativeGeoMapCopyrightNotice::paint(QPainter *painter) +{ + painter->drawImage(0, 0, m_copyrightsImage); +} + +void QDeclarativeGeoMapCopyrightNotice::mousePressEvent(QMouseEvent *event) +{ + if (m_copyrightsHtml) { + m_activeAnchor = m_copyrightsHtml->documentLayout()->anchorAt(event->pos()); + if (!m_activeAnchor.isEmpty()) + return; + } + + QQuickPaintedItem::mousePressEvent(event); +} + +void QDeclarativeGeoMapCopyrightNotice::mouseReleaseEvent(QMouseEvent *event) +{ + if (m_copyrightsHtml) { + QString anchor = m_copyrightsHtml->documentLayout()->anchorAt(event->pos()); + if (anchor == m_activeAnchor && !anchor.isEmpty()) { + emit linkActivated(anchor); + m_activeAnchor.clear(); + } + } +} + +void QDeclarativeGeoMapCopyrightNotice::rasterizeHtmlAndUpdate() +{ + if (!m_copyrightsHtml || m_copyrightsHtml->isEmpty()) + return; + + m_copyrightsImage = QImage(m_copyrightsHtml->size().toSize(), + QImage::Format_ARGB32_Premultiplied); + + m_copyrightsImage.fill(qPremultiply(QColor(Qt::transparent).rgba())); + QPainter painter(&m_copyrightsImage); + QAbstractTextDocumentLayout::PaintContext ctx; + ctx.palette.setColor(QPalette::Text, QStringLiteral("black")); + m_copyrightsHtml->documentLayout()->draw(&painter, ctx); + + setImplicitSize(m_copyrightsImage.width(), m_copyrightsImage.height()); + setContentsSize(m_copyrightsImage.size()); + + setKeepMouseGrab(true); + setAcceptedMouseButtons(Qt::LeftButton); + + update(); +} + +void QDeclarativeGeoMapCopyrightNotice::createCopyright() +{ + m_copyrightsHtml = new QTextDocument(this); + if (!m_styleSheet.isEmpty()) + m_copyrightsHtml->setDefaultStyleSheet(m_styleSheet); + + // The default 4 makes the copyright too wide and tall. + m_copyrightsHtml->setDocumentMargin(0); +} + +/*! + \internal +*/ +void QDeclarativeGeoMapCopyrightNotice::setCopyrightsVisible(bool visible) +{ + m_copyrightsVisible = visible; + + setVisible(!m_copyrightsImage.isNull() && visible); +} + +/*! + \internal +*/ +void QDeclarativeGeoMapCopyrightNotice::setCopyrightsZ(int copyrightsZ) +{ + setZ(copyrightsZ); + update(); +} + +/*! + \internal +*/ +void QDeclarativeGeoMapCopyrightNotice::copyrightsChanged(const QImage ©rightsImage) +{ + delete m_copyrightsHtml; + m_copyrightsHtml = 0; + + m_copyrightsImage = copyrightsImage; + + setImplicitSize(m_copyrightsImage.width(), m_copyrightsImage.height()); + + setKeepMouseGrab(false); + setAcceptedMouseButtons(Qt::NoButton); + setVisible(m_copyrightsVisible); + + update(); +} + +void QDeclarativeGeoMapCopyrightNotice::copyrightsChanged(const QString ©rightsHtml) +{ + if (copyrightsHtml.isEmpty()) { + setVisible(false); + return; + } else if (!m_copyrightsVisible) { + setVisible(false); + } else { + setVisible(true); + } + + // Divfy, so we can style the background. The extra <span> is a + // workaround to QTBUG-58838 and should be removed when it gets fixed. + m_html = QStringLiteral("<div id='copyright-root'><span>") + copyrightsHtml + QStringLiteral("</span></div>"); + + if (!m_copyrightsHtml) + createCopyright(); + + m_copyrightsHtml->setHtml(m_html); + rasterizeHtmlAndUpdate(); +} + +void QDeclarativeGeoMapCopyrightNotice::onCopyrightsStyleSheetChanged(const QString &styleSheet) +{ + if (m_userDefinedStyleSheet || styleSheet == m_styleSheet) + return; + + m_styleSheet = styleSheet; + if (!m_html.isEmpty() && m_copyrightsHtml) { + delete m_copyrightsHtml; + createCopyright(); + m_copyrightsHtml->setHtml(m_html); + } + rasterizeHtmlAndUpdate(); + emit styleSheetChanged(m_styleSheet); +} + +QT_END_NAMESPACE diff --git a/src/location/declarativemaps/qdeclarativegeomapcopyrightsnotice_p.h b/src/location/declarativemaps/qdeclarativegeomapcopyrightsnotice_p.h new file mode 100644 index 00000000..b09d7c1d --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeomapcopyrightsnotice_p.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Aaron McCarthy <mccarthy.aaron@gmail.com> +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEGEOMAPCOPYRIGHTSNOTICE_H +#define QDECLARATIVEGEOMAPCOPYRIGHTSNOTICE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtLocation/private/qlocationglobal_p.h> + +#include <QtGui/QImage> +#include <QtQuick/QQuickPaintedItem> + +QT_BEGIN_NAMESPACE + +class QTextDocument; +class QDeclarativeGeoMap; + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativeGeoMapCopyrightNotice : public QQuickPaintedItem +{ + Q_OBJECT + Q_PROPERTY(QDeclarativeGeoMap *mapSource READ mapSource WRITE setMapSource NOTIFY mapSourceChanged) + Q_PROPERTY(QString styleSheet READ styleSheet WRITE setStyleSheet NOTIFY styleSheetChanged) + +public: + QDeclarativeGeoMapCopyrightNotice(QQuickItem *parent = Q_NULLPTR); + ~QDeclarativeGeoMapCopyrightNotice(); + + void setCopyrightsZ(int copyrightsZ); + + void setCopyrightsVisible(bool visible); + void anchorToBottomLeft(); + + void setMapSource(QDeclarativeGeoMap *mapSource); + QDeclarativeGeoMap *mapSource(); + + QString styleSheet() const; + void setStyleSheet(const QString &styleSheet); + +public Q_SLOTS: + void copyrightsChanged(const QImage ©rightsImage); + void copyrightsChanged(const QString ©rightsHtml); + void onCopyrightsStyleSheetChanged(const QString &styleSheet); + +signals: + void linkActivated(const QString &link); + void mapSourceChanged(); + void backgroundColorChanged(const QColor &color); + void styleSheetChanged(const QString &styleSheet); + +protected: + void paint(QPainter *painter) Q_DECL_OVERRIDE; + void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE; + void mouseReleaseEvent(QMouseEvent *event) Q_DECL_OVERRIDE; + void rasterizeHtmlAndUpdate(); + +private: + void createCopyright(); + + QTextDocument *m_copyrightsHtml; + QString m_html; + QImage m_copyrightsImage; + QString m_activeAnchor; + bool m_copyrightsVisible; + QDeclarativeGeoMap *m_mapSource; + QColor m_backgroundColor; + QString m_styleSheet; + bool m_userDefinedStyleSheet; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/declarativemaps/qdeclarativegeomapitembase.cpp b/src/location/declarativemaps/qdeclarativegeomapitembase.cpp new file mode 100644 index 00000000..729825fd --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeomapitembase.cpp @@ -0,0 +1,284 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativegeomapitembase_p.h" +#include "qgeocameradata_p.h" +#include <QtLocation/private/qgeomap_p.h> +#include <QtQml/QQmlInfo> +#include <QtQuick/QSGOpacityNode> +#include <QtQuick/private/qquickmousearea_p.h> +#include <QtQuick/private/qquickitem_p.h> + +QT_BEGIN_NAMESPACE + +QGeoMapViewportChangeEvent::QGeoMapViewportChangeEvent() + : zoomLevelChanged(false), + centerChanged(false), + mapSizeChanged(false), + tiltChanged(false), + bearingChanged(false), + rollChanged(false) +{ +} + +QGeoMapViewportChangeEvent::QGeoMapViewportChangeEvent(const QGeoMapViewportChangeEvent &other) +{ + this->operator=(other); +} + +QGeoMapViewportChangeEvent &QGeoMapViewportChangeEvent::operator=(const QGeoMapViewportChangeEvent &other) +{ + if (this == &other) + return (*this); + + cameraData = other.cameraData; + mapSize = other.mapSize; + zoomLevelChanged = other.zoomLevelChanged; + centerChanged = other.centerChanged; + mapSizeChanged = other.mapSizeChanged; + tiltChanged = other.tiltChanged; + bearingChanged = other.bearingChanged; + rollChanged = other.rollChanged; + + return (*this); +} + +QDeclarativeGeoMapItemBase::QDeclarativeGeoMapItemBase(QQuickItem *parent) +: QQuickItem(parent), map_(0), quickMap_(0), parentGroup_(0) +{ + setFiltersChildMouseEvents(true); + connect(this, SIGNAL(childrenChanged()), + this, SLOT(afterChildrenChanged())); + // Changing opacity on a mapItemGroup should affect also the opacity on the children. + // This must be notified to plugins, if they are to render the item. + connect(this, &QQuickItem::opacityChanged, this, &QDeclarativeGeoMapItemBase::mapItemOpacityChanged); + parentGroup_ = qobject_cast<QDeclarativeGeoMapItemGroup *>(parent); + if (parentGroup_) + connect(qobject_cast<QDeclarativeGeoMapItemGroup *>(parent), &QQuickItem::opacityChanged, + this, &QDeclarativeGeoMapItemBase::mapItemOpacityChanged); +} + +QDeclarativeGeoMapItemBase::~QDeclarativeGeoMapItemBase() +{ + disconnect(this, SLOT(afterChildrenChanged())); + if (quickMap_) + quickMap_->removeMapItem(this); +} + +/*! + \internal +*/ +void QDeclarativeGeoMapItemBase::afterChildrenChanged() +{ + QList<QQuickItem *> kids = childItems(); + if (kids.size() > 0) { + bool printedWarning = false; + foreach (QQuickItem *i, kids) { + if (i->flags() & QQuickItem::ItemHasContents + && !qobject_cast<QQuickMouseArea *>(i)) { + if (!printedWarning) { + qmlWarning(this) << "Geographic map items do not support child items"; + printedWarning = true; + } + + qmlWarning(i) << "deleting this child"; + i->deleteLater(); + } + } + } +} + +/*! + \internal +*/ +void QDeclarativeGeoMapItemBase::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) +{ + if (quickMap == quickMap_) + return; + if (quickMap && quickMap_) + return; // don't allow association to more than one map + if (quickMap_) + quickMap_->disconnect(this); + if (map_) + map_->disconnect(this); + + quickMap_ = quickMap; + map_ = map; + + if (map_ && quickMap_) { + connect(map_, SIGNAL(cameraDataChanged(QGeoCameraData)), + this, SLOT(baseCameraDataChanged(QGeoCameraData))); + connect(quickMap, SIGNAL(heightChanged()), this, SLOT(polishAndUpdate())); + connect(quickMap, SIGNAL(widthChanged()), this, SLOT(polishAndUpdate())); + lastSize_ = QSizeF(quickMap_->width(), quickMap_->height()); + lastCameraData_ = map_->cameraData(); + } +} + +/*! + \internal +*/ +void QDeclarativeGeoMapItemBase::baseCameraDataChanged(const QGeoCameraData &cameraData) +{ + QGeoMapViewportChangeEvent evt; + evt.cameraData = cameraData; + evt.mapSize = QSizeF(quickMap_->width(), quickMap_->height()); + + if (evt.mapSize != lastSize_) + evt.mapSizeChanged = true; + + if (cameraData.bearing() != lastCameraData_.bearing()) + evt.bearingChanged = true; + if (cameraData.center() != lastCameraData_.center()) + evt.centerChanged = true; + if (cameraData.roll() != lastCameraData_.roll()) + evt.rollChanged = true; + if (cameraData.tilt() != lastCameraData_.tilt()) + evt.tiltChanged = true; + if (cameraData.zoomLevel() != lastCameraData_.zoomLevel()) + evt.zoomLevelChanged = true; + + lastSize_ = evt.mapSize; + lastCameraData_ = cameraData; + + afterViewportChanged(evt); +} + +/*! + \internal +*/ +void QDeclarativeGeoMapItemBase::setPositionOnMap(const QGeoCoordinate &coordinate, const QPointF &offset) +{ + if (!map_ || !quickMap_) + return; + + QDoubleVector2D wrappedProjection = map_->geoProjection().geoToWrappedMapProjection(coordinate); + if (!map_->geoProjection().isProjectable(wrappedProjection)) + return; + + QDoubleVector2D pos = map_->geoProjection().wrappedMapProjectionToItemPosition(wrappedProjection); + QPointF topLeft = pos.toPointF() - offset; + + setPosition(topLeft); +} + +static const double opacityRampMin = 1.5; +static const double opacityRampMax = 2.5; +/*! + \internal +*/ +float QDeclarativeGeoMapItemBase::zoomLevelOpacity() const +{ + if (quickMap_->zoomLevel() > opacityRampMax) + return 1.0; + else if (quickMap_->zoomLevel() > opacityRampMin) + return quickMap_->zoomLevel() - opacityRampMin; + else + return 0.0; +} + +bool QDeclarativeGeoMapItemBase::childMouseEventFilter(QQuickItem *item, QEvent *event) +{ + 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. + event->setAccepted(false); + return true; + } + return false; +} + +/*! + \internal +*/ +QSGNode *QDeclarativeGeoMapItemBase::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *pd) +{ + if (!map_ || !quickMap_ || map_->supportedMapItemTypes() & itemType()) { + if (oldNode) + delete oldNode; + oldNode = 0; + return 0; + } + + QSGOpacityNode *opn = static_cast<QSGOpacityNode *>(oldNode); + if (!opn) + opn = new QSGOpacityNode(); + + opn->setOpacity(zoomLevelOpacity()); + + QSGNode *oldN = opn->childCount() ? opn->firstChild() : 0; + opn->removeAllChildNodes(); + if (opn->opacity() > 0.0) { + QSGNode *n = this->updateMapItemPaintNode(oldN, pd); + if (n) + opn->appendChildNode(n); + } else { + delete oldN; + } + + return opn; +} + +/*! + \internal +*/ +QSGNode *QDeclarativeGeoMapItemBase::updateMapItemPaintNode(QSGNode *oldNode, UpdatePaintNodeData *) +{ + delete oldNode; + return 0; +} + +qreal QDeclarativeGeoMapItemBase::mapItemOpacity() const +{ + if (parentGroup_) + return parentGroup_->opacity() * opacity(); + return opacity(); +} + +bool QDeclarativeGeoMapItemBase::isPolishScheduled() const +{ + return QQuickItemPrivate::get(this)->polishScheduled; +} + +void QDeclarativeGeoMapItemBase::polishAndUpdate() +{ + polish(); + update(); +} + +QT_END_NAMESPACE diff --git a/src/location/declarativemaps/qdeclarativegeomapitembase_p.h b/src/location/declarativemaps/qdeclarativegeomapitembase_p.h new file mode 100644 index 00000000..edb97c10 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeomapitembase_p.h @@ -0,0 +1,132 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEGEOMAPITEMBASE_H +#define QDECLARATIVEGEOMAPITEMBASE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtLocation/private/qlocationglobal_p.h> + +#include <QtQuick/QQuickItem> +#include <QtPositioning/QGeoShape> + +#include <QtLocation/private/qdeclarativegeomap_p.h> +#include <QtLocation/private/qlocationglobal_p.h> +#include <QtLocation/private/qgeomap_p.h> + +QT_BEGIN_NAMESPACE + +class Q_LOCATION_PRIVATE_EXPORT QGeoMapViewportChangeEvent +{ +public: + explicit QGeoMapViewportChangeEvent(); + QGeoMapViewportChangeEvent(const QGeoMapViewportChangeEvent &other); + QGeoMapViewportChangeEvent &operator=(const QGeoMapViewportChangeEvent &other); + + QGeoCameraData cameraData; + QSizeF mapSize; + + bool zoomLevelChanged; + bool centerChanged; + bool mapSizeChanged; + bool tiltChanged; + bool bearingChanged; + bool rollChanged; +}; + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativeGeoMapItemBase : public QQuickItem +{ + Q_OBJECT + + Q_PROPERTY(QGeoShape geoShape READ geoShape STORED false ) +public: + explicit QDeclarativeGeoMapItemBase(QQuickItem *parent = 0); + virtual ~QDeclarativeGeoMapItemBase(); + + virtual void setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map); + virtual void setPositionOnMap(const QGeoCoordinate &coordinate, const QPointF &offset); + + QDeclarativeGeoMap *quickMap() { return quickMap_; } + QGeoMap *map() { return map_; } + virtual const QGeoShape &geoShape() const = 0; + + QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *); + virtual QSGNode *updateMapItemPaintNode(QSGNode *, UpdatePaintNodeData *); + + virtual QGeoMap::ItemType itemType() const = 0; + qreal mapItemOpacity() const; + +Q_SIGNALS: + void mapItemOpacityChanged(); + +protected Q_SLOTS: + virtual void afterChildrenChanged(); + virtual void afterViewportChanged(const QGeoMapViewportChangeEvent &event) = 0; + void polishAndUpdate(); + +protected: + float zoomLevelOpacity() const; + bool childMouseEventFilter(QQuickItem *item, QEvent *event); + bool isPolishScheduled() const; + +private Q_SLOTS: + void baseCameraDataChanged(const QGeoCameraData &camera); + +private: + QGeoMap *map_; + QDeclarativeGeoMap *quickMap_; + + QSizeF lastSize_; + QGeoCameraData lastCameraData_; + + QDeclarativeGeoMapItemGroup *parentGroup_; + + friend class QDeclarativeGeoMap; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/declarativemaps/qdeclarativegeomapitemgroup.cpp b/src/location/declarativemaps/qdeclarativegeomapitemgroup.cpp new file mode 100644 index 00000000..ee382353 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeomapitemgroup.cpp @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativegeomapitemgroup_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \qmltype MapItemGroup + \instantiates QDeclarativeGeoMapItemGroup + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-maps + \since Qt Location 5.9 + + \brief The MapItemGroup type is a container for map items. + + Its purpose is to enable code modularization by allowing the usage + of qml files containing map elements related to each other, and + the associated bindings. + + \note The release of this API with Qt 5.9 is a Technology Preview. + + \section2 Example Usage + + The following snippet shows how to use a MapItemGroup to create a MapCircle, centered at + the coordinate (63, -18) with a radius of 100km, filled in red, surrounded by an ondulated green border, + both contained in a semitransparent blue circle with a MouseArea that moves the whole group. + This group is defined in a separate file named PolygonGroup.qml: + + \code + import QtQuick 2.4 + import QtPositioning 5.6 + import QtLocation 5.9 + + MapItemGroup { + id: itemGroup + property alias position: mainCircle.center + property var radius: 100 * 1000 + property var borderHeightPct : 0.3 + + MapCircle { + id: mainCircle + center : QtPositioning.coordinate(40, 0) + radius: itemGroup.radius * (1.0 + borderHeightPct) + opacity: 0.05 + visible: true + color: 'blue' + + MouseArea{ + anchors.fill: parent + drag.target: parent + id: maItemGroup + } + } + + MapCircle { + id: groupCircle + center: itemGroup.position + radius: itemGroup.radius + color: 'crimson' + + onCenterChanged: { + groupPolyline.populateBorder(); + } + } + + MapPolyline { + id: groupPolyline + line.color: 'green' + line.width: 3 + + function populateBorder() { + groupPolyline.path = [] // clearing the path + var waveLength = 8.0; + var waveAmplitude = groupCircle.radius * borderHeightPct; + for (var i=0; i <= 360; i++) { + var wavePhase = (i/360.0 * 2.0 * Math.PI )* waveLength + var waveHeight = (Math.cos(wavePhase) + 1.0) / 2.0 + groupPolyline.addCoordinate(groupCircle.center.atDistanceAndAzimuth(groupCircle.radius + waveAmplitude * waveHeight , i)) + } + } + + Component.onCompleted: { + populateBorder() + } + } + } + \endcode + + PolygonGroup.qml is now a reusable component that can then be used in a Map as: + + \code + Map { + id: map + PolygonGroup { + id: polygonGroup + position: QtPositioning.coordinate(63,-18) + } + } + \endcode + + \image api-mapitemgroup.png +*/ + +QDeclarativeGeoMapItemGroup::QDeclarativeGeoMapItemGroup(QQuickItem *parent): QQuickItem(parent) +{ + +} + +QDeclarativeGeoMapItemGroup::~QDeclarativeGeoMapItemGroup() +{ + +} + +QT_END_NAMESPACE diff --git a/src/location/declarativemaps/qdeclarativegeomapitemgroup_p.h b/src/location/declarativemaps/qdeclarativegeomapitemgroup_p.h new file mode 100644 index 00000000..1b008d71 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeomapitemgroup_p.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEGEOMAPITEMGROUP_P_H +#define QDECLARATIVEGEOMAPITEMGROUP_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtLocation/private/qlocationglobal_p.h> +#include <QtQuick/QQuickItem> + +QT_BEGIN_NAMESPACE + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativeGeoMapItemGroup : public QQuickItem +{ + Q_OBJECT +public: + explicit QDeclarativeGeoMapItemGroup(QQuickItem *parent = 0); + virtual ~QDeclarativeGeoMapItemGroup(); + +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativeGeoMapItemGroup) + +#endif // QDECLARATIVEGEOMAPITEMGROUP_P_H diff --git a/src/location/declarativemaps/qdeclarativegeomapitemview.cpp b/src/location/declarativemaps/qdeclarativegeomapitemview.cpp new file mode 100644 index 00000000..a17bf672 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeomapitemview.cpp @@ -0,0 +1,539 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Jolla Ltd. +** Contact: Aaron McCarthy <aaron.mccarthy@jollamobile.com> +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativegeomapitemview_p.h" +#include "qdeclarativegeomapitemview_p_p.h" +#include "qdeclarativegeomap_p.h" +#include "qdeclarativegeomapitembase_p.h" +#include "mapitemviewdelegateincubator_p.h" + +#include <QtCore/QAbstractItemModel> +#include <QtQml/QQmlContext> +#include <QtQml/QQmlIncubator> +#include <QtQml/private/qqmlopenmetaobject_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype MapItemView + \instantiates QDeclarativeGeoMapItemView + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-maps + \since Qt Location 5.5 + \inherits QObject + + \brief The MapItemView is used to populate Map from a model. + + The MapItemView is used to populate Map with MapItems from a model. + The MapItemView type only makes sense when contained in a Map, + meaning that it has no standalone presentation. + + \section2 Example Usage + + This example demonstrates how to use the MapViewItem object to display + a \l{Route}{route} on a \l{Map}{map}: + + \snippet declarative/maps.qml QtQuick import + \snippet declarative/maps.qml QtLocation import + \codeline + \snippet declarative/maps.qml MapRoute +*/ + +QDeclarativeGeoMapItemView::QDeclarativeGeoMapItemView(QQuickItem *parent) + : QObject(parent), componentCompleted_(false), delegate_(0), + itemModel_(0), map_(0), fitViewport_(false), m_metaObjectType(0), + m_readyIncubators(0), m_repopulating(false) +{ +} + +QDeclarativeGeoMapItemView::~QDeclarativeGeoMapItemView() +{ + removeInstantiatedItems(); + if (m_metaObjectType) + m_metaObjectType->release(); +} + +/*! + \internal +*/ +void QDeclarativeGeoMapItemView::componentComplete() +{ + componentCompleted_ = true; +} + +void QDeclarativeGeoMapItemView::incubatorStatusChanged(MapItemViewDelegateIncubator *incubator, + QQmlIncubator::Status status, + bool batched) +{ + if (status == QQmlIncubator::Loading) + return; + + QDeclarativeGeoMapItemViewItemData *itemData = incubator->m_itemData; + if (!itemData) { + // Should never get here + qWarning() << "MapItemViewDelegateIncubator incubating invalid itemData"; + return; + } + + switch (status) { + case QQmlIncubator::Ready: + { + QDeclarativeGeoMapItemBase *item = qobject_cast<QDeclarativeGeoMapItemBase *>(incubator->object()); + if (!item) + break; + itemData->item = item; + if (!itemData->item) { + qWarning() << "QDeclarativeGeoMapItemView map item delegate is of unsupported type."; + delete incubator->object(); + } else { + if (!batched) { + map_->addMapItem(itemData->item); + fitViewport(); + } else { + ++m_readyIncubators; // QSemaphore not needed as multiple threads not involved + + if (m_readyIncubators == m_itemDataBatched.size()) { + + // Clearing stuff older than the reset + foreach (QDeclarativeGeoMapItemViewItemData *i, m_itemData) + removeItemData(i); + m_itemData.clear(); + + // Adding everthing created after reset was issued + foreach (QDeclarativeGeoMapItemViewItemData *i, m_itemDataBatched) { + map_->addMapItem(i->item); + } + m_itemData = m_itemDataBatched; + m_itemDataBatched.clear(); + + m_readyIncubators = 0; + m_repopulating = false; + + fitViewport(); + } + } + } + delete itemData->incubator; + itemData->incubator = 0; + break; + } + case QQmlIncubator::Null: + // Should never get here + delete itemData->incubator; + itemData->incubator = 0; + break; + case QQmlIncubator::Error: + qWarning() << "QDeclarativeGeoMapItemView map item creation failed."; + delete itemData->incubator; + itemData->incubator = 0; + break; + default: + ; + } +} + +/*! + \qmlproperty model QtLocation::MapItemView::model + + This property holds the model that provides data used for creating the map items defined by the + delegate. Only QAbstractItemModel based models are supported. +*/ +QVariant QDeclarativeGeoMapItemView::model() const +{ + return QVariant::fromValue(itemModel_); +} + +void QDeclarativeGeoMapItemView::setModel(const QVariant &model) +{ + QAbstractItemModel *itemModel = model.value<QAbstractItemModel *>(); + if (itemModel == itemModel_) + return; + + if (itemModel_) { + disconnect(itemModel_, SIGNAL(modelReset()), this, SLOT(itemModelReset())); + disconnect(itemModel_, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(itemModelRowsRemoved(QModelIndex,int,int))); + disconnect(itemModel_, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(itemModelRowsInserted(QModelIndex,int,int))); + disconnect(itemModel_, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), + this, SLOT(itemModelRowsMoved(QModelIndex,int,int,QModelIndex,int))); + disconnect(itemModel_, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)), + this, SLOT(itemModelDataChanged(QModelIndex,QModelIndex,QVector<int>))); + + removeInstantiatedItems(); // this also terminates ongong repopulations. + m_metaObjectType->release(); + m_metaObjectType = 0; + + itemModel_ = 0; + } + + if (itemModel) { + itemModel_ = itemModel; + connect(itemModel_, SIGNAL(modelReset()), this, SLOT(itemModelReset())); + connect(itemModel_, SIGNAL(rowsRemoved(QModelIndex,int,int)), + this, SLOT(itemModelRowsRemoved(QModelIndex,int,int))); + connect(itemModel_, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(itemModelRowsInserted(QModelIndex,int,int))); + connect(itemModel_, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), + this, SLOT(itemModelRowsMoved(QModelIndex,int,int,QModelIndex,int))); + connect(itemModel_, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)), + this, SLOT(itemModelDataChanged(QModelIndex,QModelIndex,QVector<int>))); + + m_metaObjectType = new QQmlOpenMetaObjectType(&QObject::staticMetaObject, 0); + foreach (const QByteArray &name, itemModel_->roleNames()) + m_metaObjectType->createProperty(name); + + instantiateAllItems(); + } + + emit modelChanged(); +} + +/*! + \internal +*/ +void QDeclarativeGeoMapItemView::itemModelReset() +{ + repopulate(); +} + +/*! + \internal +*/ +void QDeclarativeGeoMapItemView::itemModelRowsInserted(const QModelIndex &index, int start, int end) +{ + Q_UNUSED(index) + + if (!componentCompleted_ || !map_ || !delegate_ || !itemModel_) + return; + + for (int i = start; i <= end; ++i) { + const QModelIndex insertedIndex = itemModel_->index(i, 0, index); + // If ran inside a qquickwidget which forces incubators to be synchronous, this call won't happen + // with m_repopulating == true while incubators from a model reset are still incubating. + // Note that having the model in a different thread is not supported in general. + createItemForIndex(insertedIndex, m_repopulating); + } + + fitViewport(); +} + +/*! + \internal +*/ +void QDeclarativeGeoMapItemView::itemModelRowsRemoved(const QModelIndex &index, int start, int end) +{ + Q_UNUSED(index) + + if (!componentCompleted_ || !map_ || !delegate_ || !itemModel_) + return; + + for (int i = end; i >= start; --i) { + if (m_repopulating) { + QDeclarativeGeoMapItemViewItemData *itemData = m_itemDataBatched.takeAt(i); + if (!itemData) + continue; + if (itemData->incubator) { + if (itemData->incubator->isReady()) { + --m_readyIncubators; + delete itemData->incubator->object(); + } + itemData->incubator->clear(); + } + delete itemData; + } else { + QDeclarativeGeoMapItemViewItemData *itemData = m_itemData.takeAt(i); + removeItemData(itemData); + } + } + + fitViewport(); +} + +void QDeclarativeGeoMapItemView::itemModelRowsMoved(const QModelIndex &parent, int start, int end, + const QModelIndex &destination, int row) +{ + Q_UNUSED(parent) + Q_UNUSED(start) + Q_UNUSED(end) + Q_UNUSED(destination) + Q_UNUSED(row) + + qWarning() << "QDeclarativeGeoMapItemView does not support models that move rows."; +} + +void QDeclarativeGeoMapItemView::itemModelDataChanged(const QModelIndex &topLeft, + const QModelIndex &bottomRight, + const QVector<int> &roles) +{ + Q_UNUSED(roles) + + if (!m_itemData.count() || (m_repopulating && !m_itemDataBatched.count()) ) + return; + + for (int i = topLeft.row(); i <= bottomRight.row(); ++i) { + const QModelIndex index = itemModel_->index(i, 0); + QDeclarativeGeoMapItemViewItemData *itemData; + if (m_repopulating) + itemData= m_itemDataBatched.at(i); + else + itemData= m_itemData.at(i); + + QHashIterator<int, QByteArray> iterator(itemModel_->roleNames()); + while (iterator.hasNext()) { + iterator.next(); + + QVariant modelData = itemModel_->data(index, iterator.key()); + if (!modelData.isValid()) + continue; + + itemData->context->setContextProperty(QString::fromLatin1(iterator.value().constData()), + modelData); + itemData->modelDataMeta->setValue(iterator.value(), modelData); + } + } +} + +/*! + \qmlproperty Component QtLocation::MapItemView::delegate + + This property holds the delegate which defines how each item in the + model should be displayed. The Component must contain exactly one + MapItem -derived object as the root object. +*/ +QQmlComponent *QDeclarativeGeoMapItemView::delegate() const +{ + return delegate_; +} + +void QDeclarativeGeoMapItemView::setDelegate(QQmlComponent *delegate) +{ + if (delegate_ == delegate) + return; + + delegate_ = delegate; + + repopulate(); + emit delegateChanged(); +} + +/*! + \qmlproperty Component QtLocation::MapItemView::autoFitViewport + + This property controls whether to automatically pan and zoom the viewport + to display all map items when items are added or removed. + + Defaults to false. +*/ +bool QDeclarativeGeoMapItemView::autoFitViewport() const +{ + return fitViewport_; +} + +void QDeclarativeGeoMapItemView::setAutoFitViewport(const bool &fitViewport) +{ + if (fitViewport == fitViewport_) + return; + fitViewport_ = fitViewport; + emit autoFitViewportChanged(); +} + +/*! + \internal +*/ +void QDeclarativeGeoMapItemView::fitViewport() +{ + if (!map_ || !fitViewport_ || m_repopulating) + return; + + if (map_->mapItems().size() > 0) + map_->fitViewportToMapItems(); +} + +/*! + \internal +*/ +void QDeclarativeGeoMapItemView::setMap(QDeclarativeGeoMap *map) +{ + if (!map || map_) // changing map on the fly not supported + return; + map_ = map; +} + +/*! + \internal +*/ +void QDeclarativeGeoMapItemView::removeInstantiatedItems() +{ + if (!map_) + return; + + terminateOngoingRepopulation(); + foreach (QDeclarativeGeoMapItemViewItemData *itemData, m_itemData) + removeItemData(itemData); + m_itemData.clear(); +} + +/*! + \internal + + Instantiates all items. +*/ +void QDeclarativeGeoMapItemView::instantiateAllItems() +{ + if (!componentCompleted_ || !map_ || !delegate_ || !itemModel_) + return; + Q_ASSERT(!m_itemDataBatched.size()); + m_repopulating = true; + + // QQuickWidget forces incubators to synchronous mode. Thus itemDataChanged gets called during the for loop below. + m_itemDataBatched.resize(itemModel_->rowCount()); + for (int i = 0; i < itemModel_->rowCount(); ++i) { + const QModelIndex index = itemModel_->index(i, 0); + createItemForIndex(index, true); + } + + fitViewport(); +} + +void QDeclarativeGeoMapItemView::removeItemData(QDeclarativeGeoMapItemViewItemData *itemData) +{ + if (!itemData) + return; + if (itemData->incubator) { + if (itemData->incubator->isReady()) { + if (itemData->incubator->object() == itemData->item) { + map_->removeMapItem(itemData->item); // removeMapItem checks whether the item is in the map, so it's safe to call. + itemData->item = 0; + } + delete itemData->incubator->object(); + } + itemData->incubator->clear(); // stops ongoing incubation + } + if (itemData->item) + map_->removeMapItem(itemData->item); + delete itemData; // destroys the ->item too. +} + +void QDeclarativeGeoMapItemView::terminateOngoingRepopulation() +{ + if (m_repopulating) { + // Terminate the previous resetting task. Not all incubators finished, but + // QQmlIncubatorController operates in the same thread, so it is safe + // to check, here, whether incubators are ready or not, without having + // to race with them. + + foreach (QDeclarativeGeoMapItemViewItemData *itemData, m_itemDataBatched) + removeItemData(itemData); + + m_itemDataBatched.clear(); + m_readyIncubators = 0; + m_repopulating = false; + } +} + +/*! + \internal + Removes and repopulates all items. +*/ +void QDeclarativeGeoMapItemView::repopulate() +{ + if (!itemModel_ || !itemModel_->rowCount()) { + removeInstantiatedItems(); + } else { + terminateOngoingRepopulation(); + instantiateAllItems(); // removal of instantiated item done at incubation completion + } +} + +/*! + \internal + + Note: this call is async. that is returns to the event loop before returning to the caller. + May also trigger incubatorStatusChanged() before returning to the caller if the incubator is fast enough. +*/ +void QDeclarativeGeoMapItemView::createItemForIndex(const QModelIndex &index, bool batched) +{ + // Expected to be already tested by caller. + Q_ASSERT(delegate_); + Q_ASSERT(itemModel_); + + QDeclarativeGeoMapItemViewItemData *itemData = new QDeclarativeGeoMapItemViewItemData; + + itemData->modelData = new QObject; + itemData->modelDataMeta = new QQmlOpenMetaObject(itemData->modelData, m_metaObjectType, false); + itemData->context = new QQmlContext(qmlContext(this)); + + QHashIterator<int, QByteArray> iterator(itemModel_->roleNames()); + while (iterator.hasNext()) { + iterator.next(); + + QVariant modelData = itemModel_->data(index, iterator.key()); + if (!modelData.isValid()) + continue; + + itemData->context->setContextProperty(QString::fromLatin1(iterator.value().constData()), + modelData); + + itemData->modelDataMeta->setValue(iterator.value(), modelData); + } + + itemData->context->setContextProperty(QLatin1String("model"), itemData->modelData); + itemData->context->setContextProperty(QLatin1String("index"), index.row()); + + if (batched || m_repopulating) { + if (index.row() < m_itemDataBatched.size()) + m_itemDataBatched.replace(index.row(), itemData); + else + m_itemDataBatched.insert(index.row(), itemData); + } else + m_itemData.insert(index.row(), itemData); + itemData->incubator = new MapItemViewDelegateIncubator(this, itemData, batched || m_repopulating); + + delegate_->create(*itemData->incubator, itemData->context); +} + +QDeclarativeGeoMapItemViewItemData::~QDeclarativeGeoMapItemViewItemData() +{ + delete incubator; + delete item; + delete context; + delete modelData; +} + +QT_END_NAMESPACE diff --git a/src/location/declarativemaps/qdeclarativegeomapitemview_p.h b/src/location/declarativemaps/qdeclarativegeomapitemview_p.h new file mode 100644 index 00000000..8c83e37d --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeomapitemview_p.h @@ -0,0 +1,152 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Jolla Ltd. +** Contact: Aaron McCarthy <aaron.mccarthy@jollamobile.com> +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEGEOMAPITEMVIEW_H +#define QDECLARATIVEGEOMAPITEMVIEW_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtLocation/private/qlocationglobal_p.h> + +#include <QtCore/QModelIndex> +#include <QtQml/QQmlParserStatus> +#include <QtQml/QQmlIncubator> +#include <QtQml/qqml.h> + +QT_BEGIN_NAMESPACE + +class QAbstractItemModel; +class QQmlComponent; +class QQuickItem; +class QDeclarativeGeoMap; +class QDeclarativeGeoMapItemBase; +class QQmlOpenMetaObject; +class QQmlOpenMetaObjectType; +class MapItemViewDelegateIncubator; +class QDeclarativeGeoMapItemViewItemData; + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativeGeoMapItemView : public QObject, public QQmlParserStatus +{ + Q_OBJECT + + Q_INTERFACES(QQmlParserStatus) + + Q_PROPERTY(QVariant model READ model WRITE setModel NOTIFY modelChanged) + Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged) + Q_PROPERTY(bool autoFitViewport READ autoFitViewport WRITE setAutoFitViewport NOTIFY autoFitViewportChanged) + +public: + explicit QDeclarativeGeoMapItemView(QQuickItem *parent = 0); + ~QDeclarativeGeoMapItemView(); + + QVariant model() const; + void setModel(const QVariant &); + + QQmlComponent *delegate() const; + void setDelegate(QQmlComponent *); + + bool autoFitViewport() const; + void setAutoFitViewport(const bool &); + + void setMap(QDeclarativeGeoMap *); + void repopulate(); + void removeInstantiatedItems(); + void instantiateAllItems(); + + qreal zValue(); + void setZValue(qreal zValue); + + // From QQmlParserStatus + virtual void componentComplete(); + void classBegin() {} + +Q_SIGNALS: + void modelChanged(); + void delegateChanged(); + void autoFitViewportChanged(); + +protected: + void incubatorStatusChanged(MapItemViewDelegateIncubator *incubator, + QQmlIncubator::Status status, + bool batched); + +private Q_SLOTS: + void itemModelReset(); + void itemModelRowsInserted(const QModelIndex &index, int start, int end); + void itemModelRowsRemoved(const QModelIndex &index, int start, int end); + void itemModelRowsMoved(const QModelIndex &parent, int start, int end, + const QModelIndex &destination, int row); + void itemModelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, + const QVector<int> &roles); + +private: + void createItemForIndex(const QModelIndex &index, bool batched = false); + void fitViewport(); + void terminateOngoingRepopulation(); + void removeItemData(QDeclarativeGeoMapItemViewItemData *itemData); + + bool componentCompleted_; + QQmlComponent *delegate_; + QAbstractItemModel *itemModel_; + QDeclarativeGeoMap *map_; + QVector<QDeclarativeGeoMapItemViewItemData *> m_itemData; + QVector<QDeclarativeGeoMapItemViewItemData *> m_itemDataBatched; + bool fitViewport_; + + QQmlOpenMetaObjectType *m_metaObjectType; + int m_readyIncubators; + bool m_repopulating; + + friend class QDeclarativeGeoMapItemViewItemData; + friend class MapItemViewDelegateIncubator; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativeGeoMapItemView) + +#endif diff --git a/src/location/declarativemaps/qdeclarativegeomapitemview_p_p.h b/src/location/declarativemaps/qdeclarativegeomapitemview_p_p.h new file mode 100644 index 00000000..3ad3ceb4 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeomapitemview_p_p.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEGEOMAPITEMVIEW_P_P_H +#define QDECLARATIVEGEOMAPITEMVIEW_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/QModelIndex> +#include <QtQml/QQmlParserStatus> +#include <QtQml/QQmlIncubator> +#include <QtQml/qqml.h> +#include <QtQml/private/qqmlopenmetaobject_p.h> + +QT_BEGIN_NAMESPACE + +class MapItemViewDelegateIncubator; +class QDeclarativeGeoMapItemView; +class QDeclarativeGeoMapItemBase; + +class QDeclarativeGeoMapItemViewItemData +{ +public: + QDeclarativeGeoMapItemViewItemData() + : incubator(0), item(0), context(0), modelData(0), modelDataMeta(0) + { + } + + ~QDeclarativeGeoMapItemViewItemData(); + + MapItemViewDelegateIncubator *incubator; + QDeclarativeGeoMapItemBase *item; + QQmlContext *context; + QObject *modelData; + QQmlOpenMetaObject *modelDataMeta; + + friend class MapItemViewDelegateIncubator; + friend class QDeclarativeGeoMapItemView; +}; + +Q_DECLARE_TYPEINFO(QDeclarativeGeoMapItemViewItemData, Q_MOVABLE_TYPE); + +QT_END_NAMESPACE + +#endif // QDECLARATIVEGEOMAPITEMVIEW_P_P_H diff --git a/src/location/declarativemaps/qdeclarativegeomapparameter.cpp b/src/location/declarativemaps/qdeclarativegeomapparameter.cpp new file mode 100644 index 00000000..a0e05436 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeomapparameter.cpp @@ -0,0 +1,125 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativegeomapparameter_p.h" + +#include <QByteArray> +#include <QMetaObject> +#include <QMetaProperty> +#include <QSignalMapper> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype MapParameter + \instantiates QDeclarativeGeoMapParameter + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-maps + \since Qt Location 5.9 + + \brief The MapParameter type represents a parameter for a Map element. + This type provides a mean to specify plugin-dependent optional parameters + for a map. + + MapParameters by default contain only the \l type property, and + are highly plugin-dependent. + For this reason, additional properties have to be defined inside a + MapParameter at declaration time, using the QML syntax "property var foo". + + What properties have to be put inside a particular MapParameter type for + a particular plugin can be found in the documentation of the plugin. + Note that MapProperties are \b optional. + By not specifying any of them, the Map will have the default behavior. + + The release of this API with Qt 5.9 is a Technology Preview. +*/ + +/*! + \qmlproperty string QtLocation::MapParameter::type + + Set-once property which holds a string defining the type of the MapParameter +*/ + +QDeclarativeGeoMapParameter::QDeclarativeGeoMapParameter(QObject *parent) +: QGeoMapParameter(parent), m_initialPropertyCount(metaObject()->propertyCount()), m_complete(false) +{ + +} + +QDeclarativeGeoMapParameter::~QDeclarativeGeoMapParameter() +{ +} + +bool QDeclarativeGeoMapParameter::isComponentComplete() const +{ + return m_complete; +} + +int QDeclarativeGeoMapParameter::initialPropertyCount() const +{ + return m_initialPropertyCount; +} + +void QDeclarativeGeoMapParameter::classBegin() +{ +} + +void QDeclarativeGeoMapParameter::componentComplete() +{ + for (int i = m_initialPropertyCount; i < metaObject()->propertyCount(); ++i) { + QMetaProperty property = metaObject()->property(i); + + if (!property.hasNotifySignal()) { + return; + } + + QSignalMapper *mapper = new QSignalMapper(this); + mapper->setMapping(this, i); + + const QByteArray signalName = '2' + property.notifySignal().methodSignature(); // TODO: explain why '2' + QObject::connect(this, signalName, mapper, SLOT(map())); + QObject::connect(mapper, SIGNAL(mapped(int)), this, SLOT(onPropertyUpdated(int))); + } + m_complete = true; + emit completed(this); +} + +void QDeclarativeGeoMapParameter::onPropertyUpdated(int index) +{ + emit propertyUpdated(this, metaObject()->property(index).name()); +} + +QT_END_NAMESPACE diff --git a/src/location/declarativemaps/qdeclarativegeomapparameter_p.h b/src/location/declarativemaps/qdeclarativegeomapparameter_p.h new file mode 100644 index 00000000..0f54e1b7 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeomapparameter_p.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEGEOMAPPARAMETER_P_H +#define QDECLARATIVEGEOMAPPARAMETER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtLocation/private/qlocationglobal_p.h> +#include <QtLocation/private/qgeomapparameter_p.h> +#include <QQmlParserStatus> +#include <qqml.h> + +QT_BEGIN_NAMESPACE + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativeGeoMapParameter : public QGeoMapParameter, public QQmlParserStatus +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + +public: + explicit QDeclarativeGeoMapParameter(QObject *parent = 0); + virtual ~QDeclarativeGeoMapParameter(); + + bool isComponentComplete() const; + +Q_SIGNALS: + void completed(QDeclarativeGeoMapParameter *); + +protected: + int initialPropertyCount() const; + // QQmlParserStatus implementation + void classBegin() override; + void componentComplete() override; + +private slots: + void onPropertyUpdated(int index); + +private: + int m_initialPropertyCount; + bool m_complete; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativeGeoMapParameter) + +#endif // QDECLARATIVEGEOMAPPARAMETER_P_H diff --git a/src/location/declarativemaps/qdeclarativegeomapquickitem.cpp b/src/location/declarativemaps/qdeclarativegeomapquickitem.cpp new file mode 100644 index 00000000..0976bd06 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeomapquickitem.cpp @@ -0,0 +1,422 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativegeomapquickitem_p.h" + +#include <QtCore/QScopedValueRollback> +#include <QtQml/qqmlinfo.h> +#include <QtQuick/QSGOpacityNode> +#include <QtPositioning/private/qdoublevector2d_p.h> +#include <QtQuick/private/qquickmousearea_p.h> +#include <QtLocation/private/qgeomap_p.h> + +#include <QDebug> +#include <cmath> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype MapQuickItem + \instantiates QDeclarativeGeoMapQuickItem + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-maps + \since Qt Location 5.5 + + \brief The MapQuickItem type displays an arbitrary Qt Quick object + on a Map. + + The MapQuickItem type is used to place an arbitrary Qt Quick object + on a Map at a specified location and size. Compared to floating an item + above the Map, a MapQuickItem will follow the panning (and optionally, the + zooming) of the Map as if it is on the Map surface. + + The \l{sourceItem} property contains the Qt Quick item to be drawn, which + can be any kind of visible type. + + \section2 Positioning and Sizing + + The positioning of the MapQuickItem on the Map is controlled by two + properties: \l coordinate and \l anchorPoint. If only \l coordinate is set, + it specifies a longitude/latitude coordinate for the item to be placed at. + The set coordinate will line up with the top-left corner of the contained + item when shown on the screen. + + The \l anchorPoint property provides a way to line up the coordinate with + other parts of the item than just the top-left corner, by setting a number + of pixels the item will be offset by. A simple way to think about it is + to note that the point given by \l anchorPoint on the item itself is the + point that will line up with the given \l coordinate when displayed. + + In addition to being anchored to the map, the MapQuickItem can optionally + follow the scale of the map, and change size when the Map is zoomed in or + zoomed out. This behaviour is controlled by the \l zoomLevel property. The + default behaviour if \l zoomLevel is not set is for the item to be drawn + "on the screen" rather than "on the map", so that its size remains the same + regardless of the zoom level of the Map. + + \section2 Performance + + Performance of a MapQuickItem is normally in the same ballpark as the + contained Qt Quick item alone. Overheads added amount to a translation + and (possibly) scaling of the original item, as well as a transformation + from longitude and latitude to screen position. + + \section2 Limitations + + \note Due to an implementation detail, items placed inside a + MapQuickItem will have a \c{parent} item which is not the MapQuickItem. + Refer to the MapQuickItem by its \c{id}, and avoid the use of \c{anchor} + in the \c{sourceItem}. + + \section2 Example Usage + + The following snippet shows a MapQuickItem containing an Image object, + to display a Marker on the Map. This strategy is used to show the map + markers in the MapViewer example. + + \snippet mapviewer/map/Marker.qml mqi-top + \snippet mapviewer/map/Marker.qml mqi-anchor + \snippet mapviewer/map/Marker.qml mqi-closeimage + \snippet mapviewer/map/Marker.qml mqi-close + + \image api-mapquickitem.png +*/ + +QMapQuickItemMatrix4x4::QMapQuickItemMatrix4x4(QObject *parent) : QQuickTransform(parent) { } + +void QMapQuickItemMatrix4x4::setMatrix(const QMatrix4x4 &matrix) +{ + if (m_matrix == matrix) + return; + m_matrix = matrix; + update(); +} + +void QMapQuickItemMatrix4x4::applyTo(QMatrix4x4 *matrix) const +{ + *matrix *= m_matrix; +} + + +QDeclarativeGeoMapQuickItem::QDeclarativeGeoMapQuickItem(QQuickItem *parent) +: QDeclarativeGeoMapItemBase(parent), zoomLevel_(0.0), + mapAndSourceItemSet_(false), updatingGeometry_(false), matrix_(nullptr) +{ + setFlag(ItemHasContents, true); + opacityContainer_ = new QQuickItem(this); + opacityContainer_->setParentItem(this); + opacityContainer_->setFlag(ItemHasContents, true); + setFiltersChildMouseEvents(true); +} + +QDeclarativeGeoMapQuickItem::~QDeclarativeGeoMapQuickItem() {} + +/*! + \qmlproperty coordinate MapQuickItem::coordinate + + This property holds the anchor coordinate of the MapQuickItem. The point + on the sourceItem that is specified by anchorPoint is kept aligned with + this coordinate when drawn on the map. + + In the image below, there are 3 MapQuickItems that are identical except + for the value of their anchorPoint properties. The values of anchorPoint + for each are written on top of the item. + + \image api-mapquickitem-anchor.png +*/ +void QDeclarativeGeoMapQuickItem::setCoordinate(const QGeoCoordinate &coordinate) +{ + if (coordinate_ == coordinate) + return; + + coordinate_ = coordinate; + geoshape_.setTopLeft(coordinate_); + geoshape_.setBottomRight(coordinate_); + // TODO: Handle zoomLevel != 0.0 + polishAndUpdate(); + emit coordinateChanged(); +} + +/*! + \internal +*/ +void QDeclarativeGeoMapQuickItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) +{ + QDeclarativeGeoMapItemBase::setMap(quickMap,map); + if (map && quickMap) { + connect(map, SIGNAL(cameraDataChanged(QGeoCameraData)), + this, SLOT(polishAndUpdate())); + polishAndUpdate(); + } +} +// See QQuickMultiPointTouchArea::childMouseEventFilter for reference +bool QDeclarativeGeoMapQuickItem::childMouseEventFilter(QQuickItem *receiver, QEvent *event) +{ + if (isEnabled() && isVisible()) { + switch (event->type()) { + case QEvent::MouseButtonPress: + case QEvent::TouchBegin: + dragStartCoordinate_ = coordinate_; + default: + break; + } + } + return QQuickItem::childMouseEventFilter(receiver, event); +} + +/*! + \internal +*/ +void QDeclarativeGeoMapQuickItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + if (!mapAndSourceItemSet_ || updatingGeometry_ || + newGeometry.topLeft() == oldGeometry.topLeft()) { + QDeclarativeGeoMapItemBase::geometryChanged(newGeometry, oldGeometry); + return; + } + + QGeoCoordinate newCoordinate; + // with zoomLevel set the anchorPoint has to be factored into the transformation to properly transform around it. + if (zoomLevel_ != 0.0) { + // When dragStartCoordinate_ can't be projected to screen, dragging must be disabled. + if (!map()->geoProjection().isProjectable(map()->geoProjection().geoToWrappedMapProjection(dragStartCoordinate_))) + return; + + QDoubleVector2D pos = map()->geoProjection().coordinateToItemPosition(dragStartCoordinate_, false); + // oldGeometry.topLeft() is always intended to be (0,0), even when for some reason it's not. + pos.setX(pos.x() + newGeometry.topLeft().x()); + pos.setY(pos.y() + newGeometry.topLeft().y()); + newCoordinate = map()->geoProjection().itemPositionToCoordinate(pos, false); + } else { + newCoordinate = map()->geoProjection().itemPositionToCoordinate(QDoubleVector2D(x(), y()) + QDoubleVector2D(anchorPoint_), false); + } + + if (newCoordinate.isValid()) + setCoordinate(newCoordinate); + + // Not calling QDeclarativeGeoMapItemBase::geometryChanged() as it will be called from a nested + // call to this function. +} + +/*! + \internal +*/ +QGeoCoordinate QDeclarativeGeoMapQuickItem::coordinate() +{ + return coordinate_; +} + +/*! + \qmlproperty object MapQuickItem::sourceItem + + This property holds the source item that will be drawn on the map. +*/ +void QDeclarativeGeoMapQuickItem::setSourceItem(QQuickItem *sourceItem) +{ + if (sourceItem_.data() == sourceItem) + return; + sourceItem_ = sourceItem; + polishAndUpdate(); + emit sourceItemChanged(); +} + +QQuickItem *QDeclarativeGeoMapQuickItem::sourceItem() +{ + return sourceItem_.data(); +} + +/*! + \internal +*/ +void QDeclarativeGeoMapQuickItem::afterChildrenChanged() +{ + QList<QQuickItem *> kids = childItems(); + if (kids.size() > 0) { + bool printedWarning = false; + foreach (QQuickItem *i, kids) { + if (i->flags() & QQuickItem::ItemHasContents + && !qobject_cast<QQuickMouseArea *>(i) + && sourceItem_.data() != i + && opacityContainer_ != i) { + if (!printedWarning) { + qmlWarning(this) << "Use the sourceItem property for the contained item, direct children are not supported"; + printedWarning = true; + } + + qmlWarning(i) << "deleting this child"; + i->deleteLater(); + } + } + } +} + +/*! + \qmlproperty QPointF MapQuickItem::anchorPoint + + This property determines which point on the sourceItem that will be lined + up with the coordinate on the map. +*/ +void QDeclarativeGeoMapQuickItem::setAnchorPoint(const QPointF &anchorPoint) +{ + if (anchorPoint == anchorPoint_) + return; + anchorPoint_ = anchorPoint; + polishAndUpdate(); + emit anchorPointChanged(); +} + +QPointF QDeclarativeGeoMapQuickItem::anchorPoint() const +{ + return anchorPoint_; +} + +/*! + \qmlproperty real MapQuickItem::zoomLevel + + This property controls the scaling behaviour of the contents of the + MapQuickItem. In particular, by setting this property it is possible + to choose between objects that are drawn on the screen (and sized in + screen pixels), and those drawn on the map surface (which change size + with the zoom level of the map). + + The default value for this property is 0.0, which corresponds to drawing + the object on the screen surface. If set to another value, the object will + be drawn on the map surface instead. The value (if not zero) specifies the + zoomLevel at which the object will be visible at a scale of 1:1 (ie, where + object pixels and screen pixels are the same). At zoom levels lower than + this, the object will appear smaller, and at higher zoom levels, appear + larger. This is in contrast to when this property is set to zero, where + the object remains the same size on the screen at all zoom levels. +*/ +void QDeclarativeGeoMapQuickItem::setZoomLevel(qreal zoomLevel) +{ + if (zoomLevel == zoomLevel_) + return; + zoomLevel_ = zoomLevel; + // TODO: update geoshape_! + polishAndUpdate(); + emit zoomLevelChanged(); +} + +qreal QDeclarativeGeoMapQuickItem::zoomLevel() const +{ + return zoomLevel_; +} + +const QGeoShape &QDeclarativeGeoMapQuickItem::geoShape() const +{ + // TODO: return a QGeoRectangle representing the bounding geo rectangle of the quick item + // when zoomLevel_ is != 0.0 + return geoshape_; +} + +QGeoMap::ItemType QDeclarativeGeoMapQuickItem::itemType() const +{ + return QGeoMap::MapQuickItem; +} + +/*! + \internal +*/ +void QDeclarativeGeoMapQuickItem::updatePolish() +{ + if (!quickMap() && sourceItem_) { + mapAndSourceItemSet_ = false; + sourceItem_.data()->setParentItem(0); + return; + } + + if (!quickMap() || !map() || !sourceItem_) { + mapAndSourceItemSet_ = false; + return; + } + + if (!mapAndSourceItemSet_ && quickMap() && map() && sourceItem_) { + mapAndSourceItemSet_ = true; + sourceItem_.data()->setParentItem(opacityContainer_); + sourceItem_.data()->setTransformOrigin(QQuickItem::TopLeft); + connect(sourceItem_.data(), SIGNAL(xChanged()), + this, SLOT(polishAndUpdate())); + connect(sourceItem_.data(), SIGNAL(yChanged()), + this, SLOT(polishAndUpdate())); + connect(sourceItem_.data(), SIGNAL(widthChanged()), + this, SLOT(polishAndUpdate())); + connect(sourceItem_.data(), SIGNAL(heightChanged()), + this, SLOT(polishAndUpdate())); + } + + QScopedValueRollback<bool> rollback(updatingGeometry_); + updatingGeometry_ = true; + + opacityContainer_->setOpacity(zoomLevelOpacity()); + + setWidth(sourceItem_.data()->width()); + setHeight(sourceItem_.data()->height()); + if (zoomLevel_ != 0.0) { // zoom level initialized to 0.0. If it's different, it has been set explicitly. + if (!matrix_) { + matrix_ = new QMapQuickItemMatrix4x4(this); + matrix_->appendToItem(opacityContainer_); + } + matrix_->setMatrix(map()->geoProjection().quickItemTransformation(coordinate(), anchorPoint_, zoomLevel_)); + setPosition(QPointF(0,0)); + } else { + if (matrix_) + matrix_->setMatrix(QMatrix4x4()); + setPositionOnMap(coordinate(), anchorPoint_); + } +} + +/*! + \internal +*/ +void QDeclarativeGeoMapQuickItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event) +{ + Q_UNUSED(event); +} + +/*! + \internal +*/ +qreal QDeclarativeGeoMapQuickItem::scaleFactor() +{ + qreal scale = 1.0; + // use 1+x to avoid fuzzy compare against zero + if (!qFuzzyCompare(1.0 + zoomLevel_, 1.0)) + scale = std::pow(0.5, zoomLevel_ - map()->cameraData().zoomLevel()); + return scale; +} + +QT_END_NAMESPACE diff --git a/src/location/declarativemaps/qdeclarativegeomapquickitem_p.h b/src/location/declarativemaps/qdeclarativegeomapquickitem_p.h new file mode 100644 index 00000000..2035a997 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeomapquickitem_p.h @@ -0,0 +1,135 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEGEOMAPQUICKITEM_H +#define QDECLARATIVEGEOMAPQUICKITEM_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtLocation/private/qlocationglobal_p.h> + +#include <QtQuick/QQuickItem> +#include <QtQuick/QSGNode> + +#include <QtLocation/private/qdeclarativegeomap_p.h> +#include <QtLocation/private/qdeclarativegeomapitembase_p.h> +#include <QtPositioning/qgeoshape.h> + +QT_BEGIN_NAMESPACE + +class QMapQuickItemMatrix4x4 : public QQuickTransform +{ +public: + QMapQuickItemMatrix4x4(QObject *parent = nullptr); + + void setMatrix(const QMatrix4x4& matrix); + void applyTo(QMatrix4x4 *matrix) const Q_DECL_OVERRIDE; + + QMatrix4x4 m_matrix; +}; + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativeGeoMapQuickItem : public QDeclarativeGeoMapItemBase +{ + Q_OBJECT + Q_PROPERTY(QGeoCoordinate coordinate READ coordinate WRITE setCoordinate NOTIFY coordinateChanged) + Q_PROPERTY(QPointF anchorPoint READ anchorPoint WRITE setAnchorPoint NOTIFY anchorPointChanged) + Q_PROPERTY(qreal zoomLevel READ zoomLevel WRITE setZoomLevel NOTIFY zoomLevelChanged) + Q_PROPERTY(QQuickItem *sourceItem READ sourceItem WRITE setSourceItem NOTIFY sourceItemChanged) + +public: + explicit QDeclarativeGeoMapQuickItem(QQuickItem *parent = 0); + ~QDeclarativeGeoMapQuickItem(); + + virtual void setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) Q_DECL_OVERRIDE; + + void setCoordinate(const QGeoCoordinate &coordinate); + QGeoCoordinate coordinate(); + + void setSourceItem(QQuickItem *sourceItem); + QQuickItem *sourceItem(); + + void setAnchorPoint(const QPointF &anchorPoint); + QPointF anchorPoint() const; + + void setZoomLevel(qreal zoomLevel); + qreal zoomLevel() const; + + const QGeoShape &geoShape() const Q_DECL_OVERRIDE; + QGeoMap::ItemType itemType() const Q_DECL_OVERRIDE; + +Q_SIGNALS: + void coordinateChanged(); + void sourceItemChanged(); + void anchorPointChanged(); + void zoomLevelChanged(); + +protected: + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) Q_DECL_OVERRIDE; + void updatePolish() Q_DECL_OVERRIDE; + bool childMouseEventFilter(QQuickItem *item, QEvent *event) Q_DECL_OVERRIDE; + +protected Q_SLOTS: + virtual void afterChildrenChanged() Q_DECL_OVERRIDE; + virtual void afterViewportChanged(const QGeoMapViewportChangeEvent &event) Q_DECL_OVERRIDE; + +private: + qreal scaleFactor(); + QGeoCoordinate dragStartCoordinate_; + QGeoCoordinate coordinate_; + QGeoRectangle geoshape_; + QPointer<QQuickItem> sourceItem_; + QQuickItem *opacityContainer_; + QPointF anchorPoint_; + qreal zoomLevel_; + bool mapAndSourceItemSet_; + bool updatingGeometry_; + QMapQuickItemMatrix4x4 *matrix_; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativeGeoMapQuickItem) + +#endif diff --git a/src/location/declarativemaps/qdeclarativegeomaptype.cpp b/src/location/declarativemaps/qdeclarativegeomaptype.cpp new file mode 100644 index 00000000..86444aa4 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeomaptype.cpp @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativegeomaptype_p.h" +#include <qnumeric.h> +#include <QtQml/qqml.h> +#include <QDebug> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype MapType + \instantiates QDeclarativeGeoMapType + \inherits QObject + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-maps + \since Qt Location 5.5 + + \brief The MapType type holds information about a map type. + + This includes the map type's \l name and \l description, the \l style and + a flag to indicate if the map type is optimized for mobile devices (\l mobile). +*/ + +QDeclarativeGeoMapType::QDeclarativeGeoMapType(const QGeoMapType mapType, QObject *parent) + : QObject(parent), + mapType_(mapType) {} + +QDeclarativeGeoMapType::~QDeclarativeGeoMapType() {} + +/*! + \qmlproperty enumeration MapType::style + + This read-only property gives access to the style of the map type. + + \list + \li MapType.NoMap - No map. + \li MapType.StreetMap - A street map. + \li MapType.SatelliteMapDay - A map with day-time satellite imagery. + \li MapType.SatelliteMapNight - A map with night-time satellite imagery. + \li MapType.TerrainMap - A terrain map. + \li MapType.HybridMap - A map with satellite imagery and street information. + \li MapType.GrayStreetMap - A gray-shaded street map. + \li MapType.PedestrianMap - A street map suitable for pedestriants. + \li MapType.CarNavigationMap - A street map suitable for car navigation. + \li MapType.CycleMap - A street map suitable for cyclists. + \li MapType.CustomMap - A custom map type. + \endlist +*/ +QDeclarativeGeoMapType::MapStyle QDeclarativeGeoMapType::style() const +{ + return QDeclarativeGeoMapType::MapStyle(mapType_.style()); +} + +/*! + \qmlproperty string MapType::name + + This read-only property holds the name of the map type as a single formatted string. +*/ +QString QDeclarativeGeoMapType::name() const +{ + return mapType_.name(); +} + +/*! + \qmlproperty string MapType::description + + This read-only property holds the description of the map type as a single formatted string. +*/ +QString QDeclarativeGeoMapType::description() const +{ + return mapType_.description(); +} + +/*! + \qmlproperty bool MapType::mobile + + \brief Whether the map type is optimized for the use on a mobile device. + + Map types for mobile devices usually have higher constrast to counteract the + effects of sunlight and a reduced color for improved readability. +*/ +bool QDeclarativeGeoMapType::mobile() const +{ + return mapType_.mobile(); +} + +/*! + \qmlproperty bool MapType::night + \since Qt Location 5.4 + + \brief Whether the map type is optimized for use at night. + + Map types suitable for use at night usually have a dark background. +*/ +bool QDeclarativeGeoMapType::night() const +{ + return mapType_.night(); +} + +QT_END_NAMESPACE diff --git a/src/location/declarativemaps/qdeclarativegeomaptype_p.h b/src/location/declarativemaps/qdeclarativegeomaptype_p.h new file mode 100644 index 00000000..7b449aa0 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeomaptype_p.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEGEOMAPTYPE_H +#define QDECLARATIVEGEOMAPTYPE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtLocation/private/qlocationglobal_p.h> + +#include <QtCore/QObject> +#include <QtQml/qqml.h> +#include <QtLocation/private/qgeomaptype_p.h> + +QT_BEGIN_NAMESPACE + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativeGeoMapType : public QObject +{ + Q_OBJECT + Q_ENUMS(MapStyle) + + Q_PROPERTY(MapStyle style READ style CONSTANT) + Q_PROPERTY(QString name READ name CONSTANT) + Q_PROPERTY(QString description READ description CONSTANT) + Q_PROPERTY(bool mobile READ mobile CONSTANT) + Q_PROPERTY(bool night READ night CONSTANT REVISION 1) + +public: + enum MapStyle { + NoMap = QGeoMapType::NoMap, + StreetMap = QGeoMapType::StreetMap, + SatelliteMapDay = QGeoMapType::SatelliteMapDay, + SatelliteMapNight = QGeoMapType::SatelliteMapNight, + TerrainMap = QGeoMapType::TerrainMap, + HybridMap = QGeoMapType::HybridMap, + TransitMap = QGeoMapType::TransitMap, + GrayStreetMap = QGeoMapType::GrayStreetMap, + PedestrianMap = QGeoMapType::PedestrianMap, + CarNavigationMap = QGeoMapType::CarNavigationMap, + CycleMap = QGeoMapType::CycleMap, + CustomMap = 100 + }; + + QDeclarativeGeoMapType(const QGeoMapType mapType, QObject *parent = 0); + ~QDeclarativeGeoMapType(); + + MapStyle style() const; + QString name() const; + QString description() const; + bool mobile() const; + bool night() const; + + const QGeoMapType mapType() { return mapType_; } + +private: + QGeoMapType mapType_; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativeGeoMapType) + +#endif diff --git a/src/location/declarativemaps/qdeclarativegeoroute.cpp b/src/location/declarativemaps/qdeclarativegeoroute.cpp new file mode 100644 index 00000000..bce0af80 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeoroute.cpp @@ -0,0 +1,275 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativegeoroute_p.h" +#include "locationvaluetypehelper_p.h" +#include <QtLocation/private/qgeomap_p.h> + +#include <QtQml/QQmlEngine> +#include <QtQml/qqmlinfo.h> +#include <QtQml/private/qqmlengine_p.h> +#include <QtPositioning/QGeoRectangle> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype Route + \instantiates QDeclarativeGeoRoute + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-routing + \since Qt Location 5.5 + + \brief The Route type represents one geographical route. + + A Route type contains high level information about a route, such + as the length the route, the estimated travel time for the route, + and enough information to render a basic image of the route on a map. + + The QGeoRoute object also contains a list of \l RouteSegment objects which + describe subsections of the route in greater detail. + + The primary means of acquiring Route objects is \l RouteModel. + + \section1 Example + + This example shows how to display a route's maneuvers in a ListView: + + \snippet declarative/routing.qml QtQuick import + \snippet declarative/maps.qml QtLocation import + \codeline + \snippet declarative/routing.qml Route Maneuver List1 + \snippet declarative/routing.qml Route Maneuver List2 + \snippet declarative/routing.qml Route Maneuver List3 + +*/ + +QDeclarativeGeoRoute::QDeclarativeGeoRoute(QObject *parent) + : QObject(parent) +{ + this->init(); +} + +QDeclarativeGeoRoute::QDeclarativeGeoRoute(const QGeoRoute &route, QObject *parent) + : QObject(parent), + route_(route) +{ + this->init(); +} + +QDeclarativeGeoRoute::~QDeclarativeGeoRoute() {} + +void QDeclarativeGeoRoute::init() +{ + QGeoRouteSegment segment = route_.firstRouteSegment(); + while (segment.isValid()) { + QDeclarativeGeoRouteSegment *routeSegment = new QDeclarativeGeoRouteSegment(segment, this); + QQmlEngine::setContextForObject(routeSegment, QQmlEngine::contextForObject(this)); + segments_.append(routeSegment); + segment = segment.nextRouteSegment(); + } +} + +/*! + \internal +*/ +QList<QGeoCoordinate> QDeclarativeGeoRoute::routePath() +{ + return route_.path(); +} + +/*! + \qmlproperty georectangle QtLocation::Route::bounds + + Read-only property which holds a bounding box which encompasses the entire route. + +*/ + +QGeoRectangle QDeclarativeGeoRoute::bounds() const +{ + return route_.bounds(); +} + +/*! + \qmlproperty int QtLocation::Route::travelTime + + Read-only property which holds the estimated amount of time it will take to + traverse this route, in seconds. + +*/ + +int QDeclarativeGeoRoute::travelTime() const +{ + return route_.travelTime(); +} + +/*! + \qmlproperty real QtLocation::Route::distance + + Read-only property which holds distance covered by this route, in meters. +*/ + +qreal QDeclarativeGeoRoute::distance() const +{ + return route_.distance(); +} + +/*! + \qmlproperty QJSValue QtLocation::Route::path + + Read-only property which holds the geographical coordinates of this route. + Coordinates are listed in the order in which they would be traversed by someone + traveling along this segment of the route. + + To access individual segments you can use standard list accessors: 'path.length' + indicates the number of objects and 'path[index starting from zero]' gives + the actual object. + + \sa QtPositioning::coordinate +*/ + +QJSValue QDeclarativeGeoRoute::path() const +{ + QQmlContext *context = QQmlEngine::contextForObject(parent()); + QQmlEngine *engine = context->engine(); + QV4::ExecutionEngine *v4 = QQmlEnginePrivate::getV4Engine(engine); + + QV4::Scope scope(v4); + QV4::Scoped<QV4::ArrayObject> pathArray(scope, v4->newArrayObject(route_.path().length())); + for (int i = 0; i < route_.path().length(); ++i) { + const QGeoCoordinate &c = route_.path().at(i); + + QV4::ScopedValue cv(scope, v4->fromVariant(QVariant::fromValue(c))); + pathArray->putIndexed(i, cv); + } + + return QJSValue(v4, pathArray.asReturnedValue()); +} + +void QDeclarativeGeoRoute::setPath(const QJSValue &value) +{ + if (!value.isArray()) + return; + + QList<QGeoCoordinate> pathList; + quint32 length = value.property(QStringLiteral("length")).toUInt(); + for (quint32 i = 0; i < length; ++i) { + bool ok; + QGeoCoordinate c = parseCoordinate(value.property(i), &ok); + + if (!ok || !c.isValid()) { + qmlWarning(this) << "Unsupported path type"; + return; + } + + pathList.append(c); + } + + if (route_.path() == pathList) + return; + + route_.setPath(pathList); + + emit pathChanged(); +} + +/*! + \qmlproperty list<RouteSegment> QtLocation::Route::segments + + Read-only property which holds the list of \l RouteSegment objects of this route. + + To access individual segments you can use standard list accessors: 'segments.length' + indicates the number of objects and 'segments[index starting from zero]' gives + the actual objects. + + \sa RouteSegment +*/ + +QQmlListProperty<QDeclarativeGeoRouteSegment> QDeclarativeGeoRoute::segments() +{ + return QQmlListProperty<QDeclarativeGeoRouteSegment>(this, 0, segments_append, segments_count, + segments_at, segments_clear); +} + +/*! + \internal +*/ +void QDeclarativeGeoRoute::segments_append(QQmlListProperty<QDeclarativeGeoRouteSegment> *prop, + QDeclarativeGeoRouteSegment *segment) +{ + static_cast<QDeclarativeGeoRoute *>(prop->object)->appendSegment(segment); +} + +/*! + \internal +*/ +int QDeclarativeGeoRoute::segments_count(QQmlListProperty<QDeclarativeGeoRouteSegment> *prop) +{ + return static_cast<QDeclarativeGeoRoute *>(prop->object)->segments_.count(); +} + +/*! + \internal +*/ +QDeclarativeGeoRouteSegment *QDeclarativeGeoRoute::segments_at(QQmlListProperty<QDeclarativeGeoRouteSegment> *prop, int index) +{ + return static_cast<QDeclarativeGeoRoute *>(prop->object)->segments_.at(index); +} + +/*! + \internal +*/ +void QDeclarativeGeoRoute::segments_clear(QQmlListProperty<QDeclarativeGeoRouteSegment> *prop) +{ + static_cast<QDeclarativeGeoRoute *>(prop->object)->clearSegments(); +} + +/*! + \internal +*/ +void QDeclarativeGeoRoute::appendSegment(QDeclarativeGeoRouteSegment *segment) +{ + segments_.append(segment); +} + +/*! + \internal +*/ +void QDeclarativeGeoRoute::clearSegments() +{ + segments_.clear(); +} + +QT_END_NAMESPACE diff --git a/src/location/declarativemaps/qdeclarativegeoroute_p.h b/src/location/declarativemaps/qdeclarativegeoroute_p.h new file mode 100644 index 00000000..e4501770 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeoroute_p.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEGEOROUTE_H +#define QDECLARATIVEGEOROUTE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtLocation/private/qlocationglobal_p.h> +#include <QtLocation/private/qdeclarativegeoroutesegment_p.h> + +#include <QtCore/QObject> +#include <QtQml/QQmlListProperty> +#include <QtLocation/QGeoRoute> + +QT_BEGIN_NAMESPACE + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativeGeoRoute : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QGeoRectangle bounds READ bounds CONSTANT) + Q_PROPERTY(int travelTime READ travelTime CONSTANT) + Q_PROPERTY(qreal distance READ distance CONSTANT) + Q_PROPERTY(QJSValue path READ path WRITE setPath NOTIFY pathChanged) + Q_PROPERTY(QQmlListProperty<QDeclarativeGeoRouteSegment> segments READ segments CONSTANT) + +public: + explicit QDeclarativeGeoRoute(QObject *parent = 0); + QDeclarativeGeoRoute(const QGeoRoute &route, QObject *parent = 0); + ~QDeclarativeGeoRoute(); + + QGeoRectangle bounds() const; + int travelTime() const; + qreal distance() const; + + QJSValue path() const; + void setPath(const QJSValue &value); + + QQmlListProperty<QDeclarativeGeoRouteSegment> segments(); + + void appendSegment(QDeclarativeGeoRouteSegment *segment); + void clearSegments(); + +Q_SIGNALS: + void pathChanged(); + +private: + static void segments_append(QQmlListProperty<QDeclarativeGeoRouteSegment> *prop, QDeclarativeGeoRouteSegment *segment); + static int segments_count(QQmlListProperty<QDeclarativeGeoRouteSegment> *prop); + static QDeclarativeGeoRouteSegment *segments_at(QQmlListProperty<QDeclarativeGeoRouteSegment> *prop, int index); + static void segments_clear(QQmlListProperty<QDeclarativeGeoRouteSegment> *prop); + + void init(); + QList<QGeoCoordinate> routePath(); + + QGeoRoute route_; + QList<QDeclarativeGeoRouteSegment *> segments_; + friend class QDeclarativeRouteMapItem; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/declarativemaps/qdeclarativegeoroutemodel.cpp b/src/location/declarativemaps/qdeclarativegeoroutemodel.cpp new file mode 100644 index 00000000..135323e6 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeoroutemodel.cpp @@ -0,0 +1,1306 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativegeoroutemodel_p.h" +#include "qdeclarativegeoroute_p.h" +#include "error_messages.h" +#include "locationvaluetypehelper_p.h" + +#include <QtCore/QCoreApplication> +#include <QtQml/QQmlEngine> +#include <QtQml/qqmlinfo.h> +#include <QtQml/private/qqmlengine_p.h> +#include <QtLocation/QGeoRoutingManager> +#include <QtPositioning/QGeoRectangle> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype RouteModel + \instantiates QDeclarativeGeoRouteModel + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-routing + \since Qt Location 5.5 + + \brief The RouteModel type provides access to routes. + + The RouteModel type is used as part of a model/view grouping to retrieve + geographic routes from a backend provider. Routes include data about driving + directions between two points, walking directions with multiple waypoints, + and various other similar concepts. It functions much like other Model + types in QML (see for example \l {Models and Views in Qt Quick#Models}{ListModel} and + \l XmlListModel), and interacts with views such as \l MapItemView, and \l{ListView}. + + Like \l Map and \l GeocodeModel, all the data for a RouteModel to work comes + from a services plugin. This is contained in the \l{plugin} property, and + this must be set before the RouteModel can do any useful work. + + Once the plugin is set, create a \l RouteQuery with the appropriate + waypoints and other settings, and set the RouteModel's \l{query} + property. If \l autoUpdate is enabled, the update will being automatically. + Otherwise, the \l{update} method may be used. By default, autoUpdate is + disabled. + + The data stored and returned in the RouteModel consists of \l Route objects, + as a list with the role name "routeData". See the documentation for \l Route + for further details on its structure and contents. + + \section2 Example Usage + + The following snippet is two-part, showing firstly the declaration of + objects, and secondly a short piece of procedural code using it. We set + the routeModel's \l{autoUpdate} property to false, and call \l{update} once + the query is set up, to avoid useless extra requests halfway through the + set up of the query. + + \code + Plugin { + id: aPlugin + name: "osm" + } + + RouteQuery { + id: aQuery + } + + RouteModel { + id: routeModel + plugin: aPlugin + query: aQuery + autoUpdate: false + } + \endcode + + \code + { + aQuery.addWaypoint(...) + aQuery.addWaypoint(...) + aQuery.travelModes = ... + routeModel.update() + } + \endcode + +*/ + +QDeclarativeGeoRouteModel::QDeclarativeGeoRouteModel(QObject *parent) + : QAbstractListModel(parent), + complete_(false), + plugin_(0), + routeQuery_(0), + autoUpdate_(false), + status_(QDeclarativeGeoRouteModel::Null), + error_(QDeclarativeGeoRouteModel::NoError) +{ +} + +QDeclarativeGeoRouteModel::~QDeclarativeGeoRouteModel() +{ + if (!routes_.empty()) { + qDeleteAll(routes_); + routes_.clear(); + } +} + +/*! + \qmlproperty int QtLocation::RouteModel::count + + This property holds how many routes the model currently has. + Amongst other uses, you can use this value when accessing routes + via the QtLocation::RouteModel::get -method. +*/ + +int QDeclarativeGeoRouteModel::count() const +{ + return routes_.count(); +} + +/*! + \qmlmethod void QtLocation::RouteModel::reset() + + Resets the model. All route data is cleared, any outstanding requests + are aborted and possible errors are cleared. Model status will be set + to RouteModel.Null +*/ + +void QDeclarativeGeoRouteModel::reset() +{ + if (!routes_.isEmpty()) { + beginResetModel(); + qDeleteAll(routes_); + routes_.clear(); + emit countChanged(); + emit routesChanged(); + endResetModel(); + } + + emit abortRequested(); + setError(NoError, QString()); + setStatus(QDeclarativeGeoRouteModel::Null); +} + +/*! + \qmlmethod void QtLocation::RouteModel::cancel() + + Cancels any outstanding requests and clears errors. Model status will be set to either + RouteModel.Null or RouteModel.Ready. +*/ +void QDeclarativeGeoRouteModel::cancel() +{ + emit abortRequested(); + setError(NoError, QString()); + setStatus(routes_.isEmpty() ? Null : Ready); +} + +/*! + \qmlmethod void QtLocation::RouteModel::get(int) + + Returns the Route at given index. Use \l count property to check the + amount of routes available. The routes are indexed from zero, so the accessible range + is 0...(count - 1). + + If you access out of bounds, a zero (null object) is returned and a warning is issued. +*/ + +QDeclarativeGeoRoute *QDeclarativeGeoRouteModel::get(int index) +{ + if (index < 0 || index >= routes_.count()) { + qmlWarning(this) << QStringLiteral("Index '%1' out of range").arg(index); + return 0; + } + return routes_.at(index); +} + +/*! + \internal +*/ +void QDeclarativeGeoRouteModel::componentComplete() +{ + complete_ = true; + if (autoUpdate_) { + update(); + } +} + +/*! + \internal +*/ +int QDeclarativeGeoRouteModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return routes_.count(); +} + +/*! + \internal +*/ +QVariant QDeclarativeGeoRouteModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + qmlWarning(this) << QStringLiteral("Error in indexing route model's data (invalid index)."); + return QVariant(); + } + + if (index.row() >= routes_.count()) { + qmlWarning(this) << QStringLiteral("Fatal error in indexing route model's data (index overflow)."); + return QVariant(); + } + + if (role == RouteRole) { + QObject *route = routes_.at(index.row()); + return QVariant::fromValue(route); + } + return QVariant(); +} + +QHash<int, QByteArray> QDeclarativeGeoRouteModel::roleNames() const +{ + QHash<int, QByteArray> roleNames = QAbstractListModel::roleNames(); + roleNames.insert(RouteRole, "routeData"); + return roleNames; +} + +/*! + \internal +*/ +void QDeclarativeGeoRouteModel::setPlugin(QDeclarativeGeoServiceProvider *plugin) +{ + if (plugin_ == plugin) + return; + + reset(); // reset the model + + if (plugin_) + disconnect(plugin_, SIGNAL(localesChanged()), this, SIGNAL(measurementSystemChanged())); + if (plugin) + connect(plugin, SIGNAL(localesChanged()), this, SIGNAL(measurementSystemChanged())); + + plugin_ = plugin; + + if (complete_) + emit pluginChanged(); + + if (!plugin) + return; + + if (plugin_->isAttached()) { + pluginReady(); + } else { + connect(plugin_, SIGNAL(attached()), + this, SLOT(pluginReady())); + } +} + +/*! + \internal +*/ +void QDeclarativeGeoRouteModel::pluginReady() +{ + QGeoServiceProvider *serviceProvider = plugin_->sharedGeoServiceProvider(); + QGeoRoutingManager *routingManager = serviceProvider->routingManager(); + + if (serviceProvider->error() != QGeoServiceProvider::NoError) { + QDeclarativeGeoRouteModel::RouteError newError = UnknownError; + switch (serviceProvider->error()) { + case QGeoServiceProvider::NotSupportedError: + newError = EngineNotSetError; break; + case QGeoServiceProvider::UnknownParameterError: + newError = UnknownParameterError; break; + case QGeoServiceProvider::MissingRequiredParameterError: + newError = MissingRequiredParameterError; break; + case QGeoServiceProvider::ConnectionError: + newError = CommunicationError; break; + default: + break; + } + + setError(newError, serviceProvider->errorString()); + return; + } + + if (!routingManager) { + setError(EngineNotSetError, tr("Plugin does not support routing.")); + return; + } + + connect(routingManager, SIGNAL(finished(QGeoRouteReply*)), + this, SLOT(routingFinished(QGeoRouteReply*))); + connect(routingManager, SIGNAL(error(QGeoRouteReply*,QGeoRouteReply::Error,QString)), + this, SLOT(routingError(QGeoRouteReply*,QGeoRouteReply::Error,QString))); +} + +/*! + \internal +*/ +void QDeclarativeGeoRouteModel::queryDetailsChanged() +{ + if (autoUpdate_ && complete_) + update(); +} + +/*! + \qmlproperty Plugin QtLocation::RouteModel::plugin + + This property holds the plugin that providers the actual + routing service. Note that all plugins do not necessarily + provide routing (could for example provide only geocoding or maps). + + A valid plugin must be set before the RouteModel can perform any useful + operations. + + \sa Plugin +*/ + +QDeclarativeGeoServiceProvider *QDeclarativeGeoRouteModel::plugin() const +{ + return plugin_; +} + +/*! + \internal +*/ +void QDeclarativeGeoRouteModel::setQuery(QDeclarativeGeoRouteQuery *query) +{ + if (!query || query == routeQuery_) + return; + if (routeQuery_) + routeQuery_->disconnect(this); + routeQuery_ = query; + connect(query, SIGNAL(queryDetailsChanged()), this, SLOT(queryDetailsChanged())); + if (complete_) { + emit queryChanged(); + if (autoUpdate_) + update(); + } +} + +/*! + \qmlproperty RouteQuery QtLocation::RouteModel::query + + This property holds the data of the route request. + The primary data are the waypoint coordinates and possible further + preferences (means of traveling, things to avoid on route etc). +*/ + +QDeclarativeGeoRouteQuery *QDeclarativeGeoRouteModel::query() const +{ + return routeQuery_; +} + +/*! + \internal +*/ +void QDeclarativeGeoRouteModel::setAutoUpdate(bool autoUpdate) +{ + if (autoUpdate_ == autoUpdate) + return; + autoUpdate_ = autoUpdate; + if (complete_) + emit autoUpdateChanged(); +} + +/*! + \qmlproperty bool QtLocation::RouteModel::autoUpdate + + This property controls whether the Model automatically updates in response + to changes in its attached RouteQuery. The default value of this property + is false. + + If setting this value to 'true', note that any change at all in + the RouteQuery object set in the \l{query} property will trigger a new + request to be sent. If you are adjusting many properties of the RouteQuery + with autoUpdate enabled, this can generate large numbers of useless (and + later discarded) requests. +*/ + +bool QDeclarativeGeoRouteModel::autoUpdate() const +{ + return autoUpdate_; +} + +/*! + \qmlproperty Locale::MeasurementSystem QtLocation::RouteModel::measurementSystem + + This property holds the measurement system which will be used when calculating the route. This + property is changed when the \l {QtLocation::Plugin::locales}{Plugin::locales} property of + \l {QtLocation::RouteModel::plugin}{plugin} changes. + + If setting this property it must be set after the \l {QtLocation::RouteModel::plugin}{plugin} + property is set. +*/ +void QDeclarativeGeoRouteModel::setMeasurementSystem(QLocale::MeasurementSystem ms) +{ + if (!plugin_) + return; + + QGeoServiceProvider *serviceProvider = plugin_->sharedGeoServiceProvider(); + if (!serviceProvider) + return; + + QGeoRoutingManager *routingManager = serviceProvider->routingManager(); + if (!routingManager) + return; + + if (routingManager->measurementSystem() == ms) + return; + + routingManager->setMeasurementSystem(ms); + emit measurementSystemChanged(); +} + +QLocale::MeasurementSystem QDeclarativeGeoRouteModel::measurementSystem() const +{ + if (!plugin_) + return QLocale().measurementSystem(); + + QGeoServiceProvider *serviceProvider = plugin_->sharedGeoServiceProvider(); + if (!serviceProvider) { + if (plugin_->locales().isEmpty()) + return QLocale().measurementSystem(); + + return QLocale(plugin_->locales().first()).measurementSystem(); + } + + QGeoRoutingManager *routingManager = serviceProvider->routingManager(); + if (!routingManager) { + if (plugin_->locales().isEmpty()) + return QLocale().measurementSystem(); + + return QLocale(plugin_->locales().first()).measurementSystem(); + } + + return routingManager->measurementSystem(); +} + +/*! + \internal +*/ +void QDeclarativeGeoRouteModel::setStatus(QDeclarativeGeoRouteModel::Status status) +{ + if (status_ == status) + return; + + status_ = status; + + if (complete_) + emit statusChanged(); +} + +/*! + \qmlproperty enumeration QtLocation::RouteModel::status + + This read-only property holds the current status of the model. + + \list + \li RouteModel.Null - No route requests have been issued or \l reset has been called. + \li RouteModel.Ready - Route request(s) have finished successfully. + \li RouteModel.Loading - Route request has been issued but not yet finished + \li RouteModel.Error - Routing error has occurred, details are in \l error and \l errorString + \endlist +*/ + +QDeclarativeGeoRouteModel::Status QDeclarativeGeoRouteModel::status() const +{ + return status_; +} + +/*! + \qmlproperty string QtLocation::RouteModel::errorString + + This read-only property holds the textual presentation of the latest routing error. + If no error has occurred or the model has been reset, an empty string is returned. + + An empty string may also be returned if an error occurred which has no associated + textual representation. +*/ + +QString QDeclarativeGeoRouteModel::errorString() const +{ + return errorString_; +} + +/*! + \qmlproperty enumeration QtLocation::RouteModel::error + + This read-only property holds the latest error value of the routing request. + + \list + \li RouteModel.NoError - No error has occurred. + \li RouteModel.CommunicationError - An error occurred while communicating with the service provider. + \li RouteModel.EngineNotSetError - The model's plugin property was not set or there is no routing manager associated with the plugin. + \li RouteModel.MissingRequiredParameterError - A required parameter was not specified. + \li RouteModel.ParseError - The response from the service provider was in an unrecognizable format. + \li RouteModel.UnknownError - An error occurred which does not fit into any of the other categories. + \li RouteModel.UnknownParameterError - The plugin did not recognize one of the parameters it was given. + \li RouteModel.UnsupportedOptionError - The requested operation is not supported by the routing provider. + This may happen when the loaded engine does not support a particular + type of routing request. + \endlist +*/ + +QDeclarativeGeoRouteModel::RouteError QDeclarativeGeoRouteModel::error() const +{ + return error_; +} + +void QDeclarativeGeoRouteModel::setError(RouteError error, const QString& errorString) +{ + if (error_ == error && errorString_ == errorString) + return; + error_ = error; + errorString_ = errorString; + emit errorChanged(); +} + +/*! + \qmlmethod void QtLocation::RouteModel::update() + + Instructs the RouteModel to update its data. This is most useful + when \l autoUpdate is disabled, to force a refresh when the query + has been changed. +*/ +void QDeclarativeGeoRouteModel::update() +{ + if (!complete_) + return; + + if (!plugin_) { + setError(EngineNotSetError, tr("Cannot route, plugin not set.")); + return; + } + + QGeoServiceProvider *serviceProvider = plugin_->sharedGeoServiceProvider(); + if (!serviceProvider) + return; + + QGeoRoutingManager *routingManager = serviceProvider->routingManager(); + if (!routingManager) { + setError(EngineNotSetError, tr("Cannot route, route manager not set.")); + return; + } + if (!routeQuery_) { + setError(ParseError, tr("Cannot route, valid query not set.")); + return; + } + emit abortRequested(); // Clear previous requests + QGeoRouteRequest request = routeQuery_->routeRequest(); + if (request.waypoints().count() < 2) { + setError(ParseError,tr("Not enough waypoints for routing.")); + return; + } + + setError(NoError, QString()); + + QGeoRouteReply *reply = routingManager->calculateRoute(request); + setStatus(QDeclarativeGeoRouteModel::Loading); + if (!reply->isFinished()) { + connect(this, &QDeclarativeGeoRouteModel::abortRequested, reply, &QGeoRouteReply::abort); + } else { + if (reply->error() == QGeoRouteReply::NoError) { + routingFinished(reply); + } else { + routingError(reply, reply->error(), reply->errorString()); + } + } +} + +/*! + \internal +*/ +void QDeclarativeGeoRouteModel::routingFinished(QGeoRouteReply *reply) +{ + if (!reply) + return; + reply->deleteLater(); + if (reply->error() != QGeoRouteReply::NoError) + return; + + beginResetModel(); + int oldCount = routes_.count(); + qDeleteAll(routes_); + // Convert routes to declarative + routes_.clear(); + for (int i = 0; i < reply->routes().size(); ++i) { + QDeclarativeGeoRoute *route = new QDeclarativeGeoRoute(reply->routes().at(i), this); + QQmlEngine::setContextForObject(route, QQmlEngine::contextForObject(this)); + routes_.append(route); + } + endResetModel(); + + setError(NoError, QString()); + setStatus(QDeclarativeGeoRouteModel::Ready); + + if (oldCount != 0 || routes_.count() != 0) + emit routesChanged(); + if (oldCount != routes_.count()) + emit countChanged(); +} + +/*! + \internal +*/ +void QDeclarativeGeoRouteModel::routingError(QGeoRouteReply *reply, + QGeoRouteReply::Error error, + const QString &errorString) +{ + if (!reply) + return; + reply->deleteLater(); + setError(static_cast<QDeclarativeGeoRouteModel::RouteError>(error), errorString); + setStatus(QDeclarativeGeoRouteModel::Error); +} + + +/*! + \qmltype RouteQuery + \instantiates QDeclarativeGeoRouteQuery + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-routing + \since Qt Location 5.5 + + \brief The RouteQuery type is used to provide query parameters to a + RouteModel. + + A RouteQuery contains all the parameters necessary to make a request + to a routing service, which can then populate the contents of a RouteModel. + + These parameters describe key details of the route, such as \l waypoints to + pass through, \l excludedAreas to avoid, the \l travelModes in use, as well + as detailed preferences on how to optimize the route and what features + to prefer or avoid along the path (such as toll roads, highways, etc). + + RouteQuery objects are used exclusively to fill out the value of a + RouteModel's \l{RouteModel::query}{query} property, which can then begin + the retrieval process to populate the model. + + \section2 Example Usage + + The following snipped shows an incomplete example of creating a RouteQuery + object and setting it as the value of a RouteModel's \l{RouteModel::query}{query} + property. + + \code + RouteQuery { + id: aQuery + } + + RouteModel { + query: aQuery + autoUpdate: false + } + \endcode + + For a more complete example, see the documentation for the \l{RouteModel} + type, and the Mapviewer example. + + \sa RouteModel + +*/ + +QDeclarativeGeoRouteQuery::QDeclarativeGeoRouteQuery(QObject *parent) +: QObject(parent), complete_(false), m_excludedAreaCoordinateChanged(false) +{ +} + +QDeclarativeGeoRouteQuery::~QDeclarativeGeoRouteQuery() +{ +} + +/*! + \internal +*/ +void QDeclarativeGeoRouteQuery::componentComplete() +{ + complete_ = true; +} + +/*! + \qmlproperty QList<FeatureType> RouteQuery::featureTypes + + List of features that will be considered when planning the + route. Features with a weight of NeutralFeatureWeight will not be returned. + + \list + \li RouteQuery.NoFeature - No features will be taken into account when planning the route + \li RouteQuery.TollFeature - Consider tollways when planning the route + \li RouteQuery.HighwayFeature - Consider highways when planning the route + \li RouteQuery.PublicTransitFeature - Consider public transit when planning the route + \li RouteQuery.FerryFeature - Consider ferries when planning the route + \li RouteQuery.TunnelFeature - Consider tunnels when planning the route + \li RouteQuery.DirtRoadFeature - Consider dirt roads when planning the route + \li RouteQuery.ParksFeature - Consider parks when planning the route + \li RouteQuery.MotorPoolLaneFeature - Consider motor pool lanes when planning the route + \endlist + + \sa setFeatureWeight, featureWeight +*/ + +QList<int> QDeclarativeGeoRouteQuery::featureTypes() +{ + QList<int> list; + + for (int i = 0; i < request_.featureTypes().count(); ++i) { + list.append(static_cast<int>(request_.featureTypes().at(i))); + } + return list; +} + +/*! + \qmlproperty int RouteQuery::numberAlternativeRoutes + + The number of alternative routes requested when requesting routes. + The default value is 0. +*/ + + +int QDeclarativeGeoRouteQuery::numberAlternativeRoutes() const +{ + return request_.numberAlternativeRoutes(); +} + +void QDeclarativeGeoRouteQuery::setNumberAlternativeRoutes(int numberAlternativeRoutes) +{ + if (numberAlternativeRoutes == request_.numberAlternativeRoutes()) + return; + + request_.setNumberAlternativeRoutes(numberAlternativeRoutes); + + if (complete_) { + emit numberAlternativeRoutesChanged(); + emit queryDetailsChanged(); + } +} + +/*! + \qmlproperty QJSValue RouteQuery::waypoints + + + The waypoint coordinates of the desired route. + The waypoints should be given in order from origin to destination. + Two or more coordinates are needed. + + Waypoints can be set as part of the RouteQuery type declaration or + dynamically with the functions provided. + + \sa addWaypoint, removeWaypoint, clearWaypoints +*/ + +QJSValue QDeclarativeGeoRouteQuery::waypoints() +{ + QQmlContext *context = QQmlEngine::contextForObject(parent()); + QQmlEngine *engine = context->engine(); + QV4::ExecutionEngine *v4 = QQmlEnginePrivate::getV4Engine(engine); + + QV4::Scope scope(v4); + QV4::Scoped<QV4::ArrayObject> waypointArray(scope, v4->newArrayObject(request_.waypoints().length())); + for (int i = 0; i < request_.waypoints().length(); ++i) { + const QGeoCoordinate &c = request_.waypoints().at(i); + + QV4::ScopedValue cv(scope, v4->fromVariant(QVariant::fromValue(c))); + waypointArray->putIndexed(i, cv); + } + + return QJSValue(v4, waypointArray.asReturnedValue()); +} + +void QDeclarativeGeoRouteQuery::setWaypoints(const QJSValue &value) +{ + if (!value.isArray()) + return; + + QList<QGeoCoordinate> waypointList; + quint32 length = value.property(QStringLiteral("length")).toUInt(); + for (quint32 i = 0; i < length; ++i) { + bool ok; + QGeoCoordinate c = parseCoordinate(value.property(i), &ok); + + if (!ok || !c.isValid()) { + qmlWarning(this) << "Unsupported waypoint type"; + return; + } + + waypointList.append(c); + } + + if (request_.waypoints() == waypointList) + return; + + request_.setWaypoints(waypointList); + + emit waypointsChanged(); + emit queryDetailsChanged(); +} + +/*! + \qmlproperty list<georectangle> RouteQuery::excludedAreas + + Areas that the route must not cross. + + Excluded areas can be set as part of the \l RouteQuery type declaration or + dynamically with the functions provided. + + \sa addExcludedArea, removeExcludedArea, clearExcludedAreas +*/ +QJSValue QDeclarativeGeoRouteQuery::excludedAreas() const +{ + QQmlContext *context = QQmlEngine::contextForObject(parent()); + QQmlEngine *engine = context->engine(); + QV4::ExecutionEngine *v4 = QQmlEnginePrivate::getV4Engine(engine); + + QV4::Scope scope(v4); + QV4::Scoped<QV4::ArrayObject> excludedAreasArray(scope, v4->newArrayObject(request_.excludeAreas().length())); + for (int i = 0; i < request_.excludeAreas().length(); ++i) { + const QGeoRectangle &r = request_.excludeAreas().at(i); + + QV4::ScopedValue cv(scope, v4->fromVariant(QVariant::fromValue(r))); + excludedAreasArray->putIndexed(i, cv); + } + + return QJSValue(v4, excludedAreasArray.asReturnedValue()); +} + +void QDeclarativeGeoRouteQuery::setExcludedAreas(const QJSValue &value) +{ + if (!value.isArray()) + return; + + QList<QGeoRectangle> excludedAreasList; + quint32 length = value.property(QStringLiteral("length")).toUInt(); + for (quint32 i = 0; i < length; ++i) { + bool ok; + QGeoRectangle r = parseRectangle(value.property(i), &ok); + + if (!ok || !r.isValid()) { + qmlWarning(this) << "Unsupported area type"; + return; + } + + excludedAreasList.append(r); + } + + if (request_.excludeAreas() == excludedAreasList) + return; + + request_.setExcludeAreas(excludedAreasList); + + emit excludedAreasChanged(); + emit queryDetailsChanged(); +} + +/*! + \qmlmethod void QtLocation::RouteQuery::addExcludedArea(georectangle) + + Adds the given area to excluded areas (areas that the route must not cross). + Same area can only be added once. + + \sa removeExcludedArea, clearExcludedAreas +*/ + + +void QDeclarativeGeoRouteQuery::addExcludedArea(const QGeoRectangle &area) +{ + if (!area.isValid()) + return; + + QList<QGeoRectangle> excludedAreas = request_.excludeAreas(); + + if (excludedAreas.contains(area)) + return; + + excludedAreas.append(area); + + request_.setExcludeAreas(excludedAreas); + + if (complete_) { + emit excludedAreasChanged(); + emit queryDetailsChanged(); + } +} + +/*! + \qmlmethod void QtLocation::RouteQuery::removeExcludedArea(georectangle) + + Removes the given area to excluded areas (areas that the route must not cross). + + \sa addExcludedArea, clearExcludedAreas +*/ + +void QDeclarativeGeoRouteQuery::removeExcludedArea(const QGeoRectangle &area) +{ + if (!area.isValid()) + return; + + QList<QGeoRectangle> excludedAreas = request_.excludeAreas(); + + int index = excludedAreas.lastIndexOf(area); + if (index == -1) { + qmlWarning(this) << QStringLiteral("Cannot remove nonexistent area."); + return; + } + excludedAreas.removeAt(index); + request_.setExcludeAreas(excludedAreas); + + emit excludedAreasChanged(); + emit queryDetailsChanged(); +} + +/*! + \qmlmethod void QtLocation::RouteQuery::clearExcludedAreas() + + Clears all excluded areas (areas that the route must not cross). + + \sa addExcludedArea, removeExcludedArea +*/ + +void QDeclarativeGeoRouteQuery::clearExcludedAreas() +{ + if (request_.excludeAreas().isEmpty()) + return; + + request_.setExcludeAreas(QList<QGeoRectangle>()); + + emit excludedAreasChanged(); + emit queryDetailsChanged(); +} + +/*! + \qmlmethod void QtLocation::RouteQuery::addWaypoint(coordinate) + + Appends a coordinate to the list of waypoints. Same coordinate + can be set multiple times. + + \sa removeWaypoint, clearWaypoints +*/ +void QDeclarativeGeoRouteQuery::addWaypoint(const QGeoCoordinate &waypoint) +{ + if (!waypoint.isValid()) { + qmlWarning(this) << QStringLiteral("Not adding invalid waypoint."); + return; + } + + QList<QGeoCoordinate> waypoints = request_.waypoints(); + waypoints.append(waypoint); + request_.setWaypoints(waypoints); + + if (complete_) { + emit waypointsChanged(); + emit queryDetailsChanged(); + } +} + +/*! + \qmlmethod void QtLocation::RouteQuery::removeWaypoint(coordinate) + + Removes the given from the list of waypoints. In case same coordinate + appears multiple times, the most recently added coordinate instance is + removed. + + \sa addWaypoint, clearWaypoints +*/ +void QDeclarativeGeoRouteQuery::removeWaypoint(const QGeoCoordinate &waypoint) +{ + QList<QGeoCoordinate> waypoints = request_.waypoints(); + + int index = waypoints.lastIndexOf(waypoint); + if (index == -1) { + qmlWarning(this) << QStringLiteral("Cannot remove nonexistent waypoint."); + return; + } + + waypoints.removeAt(index); + + request_.setWaypoints(waypoints); + + emit waypointsChanged(); + emit queryDetailsChanged(); +} + +/*! + \qmlmethod void QtLocation::RouteQuery::clearWaypoints() + + Clears all waypoints. + + \sa removeWaypoint, addWaypoint +*/ +void QDeclarativeGeoRouteQuery::clearWaypoints() +{ + if (request_.waypoints().isEmpty()) + return; + + request_.setWaypoints(QList<QGeoCoordinate>()); + + emit waypointsChanged(); + emit queryDetailsChanged(); +} + +/*! + \qmlmethod void QtLocation::RouteQuery::setFeatureWeight(FeatureType, FeatureWeight) + + Defines the weight to associate with a feature during the planning of a + route. + + Following lists the possible feature weights: + + \list + \li RouteQuery.NeutralFeatureWeight - The presence or absence of the feature will not affect the planning of the route + \li RouteQuery.PreferFeatureWeight - Routes which contain the feature will be preferred over those that do not + \li RouteQuery.RequireFeatureWeight - Only routes which contain the feature will be considered, otherwise no route will be returned + \li RouteQuery.AvoidFeatureWeight - Routes which do not contain the feature will be preferred over those that do + \li RouteQuery.DisallowFeatureWeight - Only routes which do not contain the feature will be considered, otherwise no route will be returned + \endlist + + \sa featureTypes, resetFeatureWeights, featureWeight + +*/ + +void QDeclarativeGeoRouteQuery::setFeatureWeight(FeatureType featureType, FeatureWeight featureWeight) +{ + if (featureType == NoFeature && !request_.featureTypes().isEmpty()) { + resetFeatureWeights(); + return; + } + + // Check if the weight changes, as we need to signal it + FeatureWeight originalWeight = static_cast<FeatureWeight>(request_.featureWeight(static_cast<QGeoRouteRequest::FeatureType>(featureType))); + if (featureWeight == originalWeight) + return; + + request_.setFeatureWeight(static_cast<QGeoRouteRequest::FeatureType>(featureType), + static_cast<QGeoRouteRequest::FeatureWeight>(featureWeight)); + if (complete_ && ((originalWeight == NeutralFeatureWeight) || (featureWeight == NeutralFeatureWeight))) { + // featureTypes should now give a different list, because the original and new weight + // were not same, and other one was neutral weight + emit featureTypesChanged(); + emit queryDetailsChanged(); + } +} + +/*! + \qmlmethod void QtLocation::RouteQuery::resetFeatureWeights() + + Resets all feature weights to their default state (NeutralFeatureWeight). + + \sa featureTypes, setFeatureWeight, featureWeight +*/ +void QDeclarativeGeoRouteQuery::resetFeatureWeights() +{ + // reset all feature types. + QList<QGeoRouteRequest::FeatureType> featureTypes = request_.featureTypes(); + for (int i = 0; i < featureTypes.count(); ++i) { + request_.setFeatureWeight(featureTypes.at(i), QGeoRouteRequest::NeutralFeatureWeight); + } + if (complete_) { + emit featureTypesChanged(); + emit queryDetailsChanged(); + } +} + +/*! + \qmlmethod FeatureWeight QtLocation::RouteQuery::featureWeight(FeatureType featureType) + + Gets the weight for the \a featureType. + + \sa featureTypes, setFeatureWeight, resetFeatureWeights +*/ + +int QDeclarativeGeoRouteQuery::featureWeight(FeatureType featureType) +{ + return request_.featureWeight(static_cast<QGeoRouteRequest::FeatureType>(featureType)); +} + +/*! + \internal +*/ +void QDeclarativeGeoRouteQuery::setTravelModes(QDeclarativeGeoRouteQuery::TravelModes travelModes) +{ + QGeoRouteRequest::TravelModes reqTravelModes; + + if (travelModes & QDeclarativeGeoRouteQuery::CarTravel) + reqTravelModes |= QGeoRouteRequest::CarTravel; + if (travelModes & QDeclarativeGeoRouteQuery::PedestrianTravel) + reqTravelModes |= QGeoRouteRequest::PedestrianTravel; + if (travelModes & QDeclarativeGeoRouteQuery::BicycleTravel) + reqTravelModes |= QGeoRouteRequest::BicycleTravel; + if (travelModes & QDeclarativeGeoRouteQuery::PublicTransitTravel) + reqTravelModes |= QGeoRouteRequest::PublicTransitTravel; + if (travelModes & QDeclarativeGeoRouteQuery::TruckTravel) + reqTravelModes |= QGeoRouteRequest::TruckTravel; + + if (reqTravelModes == request_.travelModes()) + return; + + request_.setTravelModes(reqTravelModes); + + if (complete_) { + emit travelModesChanged(); + emit queryDetailsChanged(); + } +} + + +/*! + \qmlproperty enumeration RouteQuery::segmentDetail + + The level of detail which will be used in the representation of routing segments. + + \list + \li RouteQuery.NoSegmentData - No segment data should be included with the route + \li RouteQuery.BasicSegmentData - Basic segment data will be included with the route + \endlist + + The default value is RouteQuery.BasicSegmentData +*/ + +void QDeclarativeGeoRouteQuery::setSegmentDetail(SegmentDetail segmentDetail) +{ + if (static_cast<QGeoRouteRequest::SegmentDetail>(segmentDetail) == request_.segmentDetail()) + return; + request_.setSegmentDetail(static_cast<QGeoRouteRequest::SegmentDetail>(segmentDetail)); + if (complete_) { + emit segmentDetailChanged(); + emit queryDetailsChanged(); + } +} + +QDeclarativeGeoRouteQuery::SegmentDetail QDeclarativeGeoRouteQuery::segmentDetail() const +{ + return static_cast<QDeclarativeGeoRouteQuery::SegmentDetail>(request_.segmentDetail()); +} + +/*! + \qmlproperty enumeration RouteQuery::maneuverDetail + + The level of detail which will be used in the representation of routing maneuvers. + + \list + \li RouteQuery.NoManeuvers - No maneuvers should be included with the route + \li RouteQuery.BasicManeuvers - Basic maneuvers will be included with the route + \endlist + + The default value is RouteQuery.BasicManeuvers +*/ + +void QDeclarativeGeoRouteQuery::setManeuverDetail(ManeuverDetail maneuverDetail) +{ + if (static_cast<QGeoRouteRequest::ManeuverDetail>(maneuverDetail) == request_.maneuverDetail()) + return; + request_.setManeuverDetail(static_cast<QGeoRouteRequest::ManeuverDetail>(maneuverDetail)); + if (complete_) { + emit maneuverDetailChanged(); + emit queryDetailsChanged(); + } +} + +QDeclarativeGeoRouteQuery::ManeuverDetail QDeclarativeGeoRouteQuery::maneuverDetail() const +{ + return static_cast<QDeclarativeGeoRouteQuery::ManeuverDetail>(request_.maneuverDetail()); +} + +/*! + \qmlproperty enumeration RouteQuery::travelModes + + The travel modes which should be considered during the planning of the route. + Values can be combined with OR ('|') -operator. + + \list + \li RouteQuery.CarTravel - The route will be optimized for someone who is driving a car + \li RouteQuery.PedestrianTravel - The route will be optimized for someone who is walking + \li RouteQuery.BicycleTravel - The route will be optimized for someone who is riding a bicycle + \li RouteQuery.PublicTransitTravel - The route will be optimized for someone who is making use of public transit + \li RouteQuery.TruckTravel - The route will be optimized for someone who is driving a truck + \endlist + + The default value is RouteQuery.CarTravel +*/ + +QDeclarativeGeoRouteQuery::TravelModes QDeclarativeGeoRouteQuery::travelModes() const +{ + QGeoRouteRequest::TravelModes reqTravelModes = request_.travelModes(); + QDeclarativeGeoRouteQuery::TravelModes travelModes; + + if (reqTravelModes & QGeoRouteRequest::CarTravel) + travelModes |= QDeclarativeGeoRouteQuery::CarTravel; + if (reqTravelModes & QGeoRouteRequest::PedestrianTravel) + travelModes |= QDeclarativeGeoRouteQuery::PedestrianTravel; + if (reqTravelModes & QGeoRouteRequest::BicycleTravel) + travelModes |= QDeclarativeGeoRouteQuery::BicycleTravel; + if (reqTravelModes & QGeoRouteRequest::PublicTransitTravel) + travelModes |= QDeclarativeGeoRouteQuery::PublicTransitTravel; + if (reqTravelModes & QGeoRouteRequest::TruckTravel) + travelModes |= QDeclarativeGeoRouteQuery::TruckTravel; + + return travelModes; +} + +/*! + \qmlproperty enumeration RouteQuery::routeOptimizations + + The route optimizations which should be considered during the planning of the route. + Values can be combined with OR ('|') -operator. + + \list + \li RouteQuery.ShortestRoute - Minimize the length of the journey + \li RouteQuery.FastestRoute - Minimize the traveling time for the journey + \li RouteQuery.MostEconomicRoute - Minimize the cost of the journey + \li RouteQuery.MostScenicRoute - Maximize the scenic potential of the journey + \endlist + + The default value is RouteQuery.FastestRoute +*/ + +QDeclarativeGeoRouteQuery::RouteOptimizations QDeclarativeGeoRouteQuery::routeOptimizations() const +{ + QGeoRouteRequest::RouteOptimizations reqOptimizations = request_.routeOptimization(); + QDeclarativeGeoRouteQuery::RouteOptimizations optimization; + + if (reqOptimizations & QGeoRouteRequest::ShortestRoute) + optimization |= QDeclarativeGeoRouteQuery::ShortestRoute; + if (reqOptimizations & QGeoRouteRequest::FastestRoute) + optimization |= QDeclarativeGeoRouteQuery::FastestRoute; + if (reqOptimizations & QGeoRouteRequest::MostEconomicRoute) + optimization |= QDeclarativeGeoRouteQuery::MostEconomicRoute; + if (reqOptimizations & QGeoRouteRequest::MostScenicRoute) + optimization |= QDeclarativeGeoRouteQuery::MostScenicRoute; + + return optimization; +} + +void QDeclarativeGeoRouteQuery::setRouteOptimizations(QDeclarativeGeoRouteQuery::RouteOptimizations optimization) +{ + QGeoRouteRequest::RouteOptimizations reqOptimizations; + + if (optimization & QDeclarativeGeoRouteQuery::ShortestRoute) + reqOptimizations |= QGeoRouteRequest::ShortestRoute; + if (optimization & QDeclarativeGeoRouteQuery::FastestRoute) + reqOptimizations |= QGeoRouteRequest::FastestRoute; + if (optimization & QDeclarativeGeoRouteQuery::MostEconomicRoute) + reqOptimizations |= QGeoRouteRequest::MostEconomicRoute; + if (optimization & QDeclarativeGeoRouteQuery::MostScenicRoute) + reqOptimizations |= QGeoRouteRequest::MostScenicRoute; + + if (reqOptimizations == request_.routeOptimization()) + return; + + request_.setRouteOptimization(reqOptimizations); + + if (complete_) { + emit routeOptimizationsChanged(); + emit queryDetailsChanged(); + } +} + +/*! + \internal +*/ +QGeoRouteRequest QDeclarativeGeoRouteQuery::routeRequest() const +{ + return request_; +} + +void QDeclarativeGeoRouteQuery::excludedAreaCoordinateChanged() +{ + if (!m_excludedAreaCoordinateChanged) { + m_excludedAreaCoordinateChanged = true; + QMetaObject::invokeMethod(this, "doCoordinateChanged", Qt::QueuedConnection); + } +} + +void QDeclarativeGeoRouteQuery::doCoordinateChanged() +{ + m_excludedAreaCoordinateChanged = false; + emit queryDetailsChanged(); +} + +QT_END_NAMESPACE diff --git a/src/location/declarativemaps/qdeclarativegeoroutemodel_p.h b/src/location/declarativemaps/qdeclarativegeoroutemodel_p.h new file mode 100644 index 00000000..3dfd2ce6 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeoroutemodel_p.h @@ -0,0 +1,344 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEGEOROUTEMODEL_H +#define QDECLARATIVEGEOROUTEMODEL_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtLocation/private/qlocationglobal_p.h> +#include <QtLocation/private/qdeclarativegeoserviceprovider_p.h> + +#include <QtPositioning/QGeoCoordinate> +#include <QtPositioning/QGeoRectangle> + +#include <qgeorouterequest.h> +#include <qgeoroutereply.h> + +#include <QtQml/qqml.h> +#include <QtQml/QQmlParserStatus> +#include <QtQml/private/qv4engine_p.h> +#include <QAbstractListModel> + +#include <QObject> + +QT_BEGIN_NAMESPACE + +class QGeoServiceProvider; +class QGeoRoutingManager; +class QDeclarativeGeoRoute; +class QDeclarativeGeoRouteQuery; + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativeGeoRouteModel : public QAbstractListModel, public QQmlParserStatus +{ + Q_OBJECT + Q_ENUMS(Status) + Q_ENUMS(RouteError) + + Q_PROPERTY(QDeclarativeGeoServiceProvider *plugin READ plugin WRITE setPlugin NOTIFY pluginChanged) + Q_PROPERTY(QDeclarativeGeoRouteQuery *query READ query WRITE setQuery NOTIFY queryChanged) + Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(bool autoUpdate READ autoUpdate WRITE setAutoUpdate NOTIFY autoUpdateChanged) + Q_PROPERTY(Status status READ status NOTIFY statusChanged) + Q_PROPERTY(QString errorString READ errorString NOTIFY errorChanged) + Q_PROPERTY(RouteError error READ error NOTIFY errorChanged) + Q_PROPERTY(QLocale::MeasurementSystem measurementSystem READ measurementSystem WRITE setMeasurementSystem NOTIFY measurementSystemChanged) + + Q_INTERFACES(QQmlParserStatus) + +public: + enum Roles { + RouteRole = Qt::UserRole + 500 + }; + + enum Status { + Null, + Ready, + Loading, + Error + }; + + enum RouteError { + NoError = QGeoRouteReply::NoError, + EngineNotSetError = QGeoRouteReply::EngineNotSetError, + CommunicationError = QGeoRouteReply::CommunicationError, + ParseError = QGeoRouteReply::ParseError, + UnsupportedOptionError = QGeoRouteReply::UnsupportedOptionError, + UnknownError = QGeoRouteReply::UnknownError, + //we leave gap for future QGeoRouteReply errors + + //QGeoServiceProvider related errors start here + UnknownParameterError = 100, + MissingRequiredParameterError + }; + + explicit QDeclarativeGeoRouteModel(QObject *parent = 0); + ~QDeclarativeGeoRouteModel(); + + // From QQmlParserStatus + void classBegin() {} + void componentComplete(); + + // From QAbstractListModel + int rowCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + virtual QHash<int,QByteArray> roleNames() const; + + void setPlugin(QDeclarativeGeoServiceProvider *plugin); + QDeclarativeGeoServiceProvider *plugin() const; + + void setQuery(QDeclarativeGeoRouteQuery *query); + QDeclarativeGeoRouteQuery *query() const; + + void setAutoUpdate(bool autoUpdate); + bool autoUpdate() const; + + void setMeasurementSystem(QLocale::MeasurementSystem ms); + QLocale::MeasurementSystem measurementSystem() const; + + Status status() const; + QString errorString() const; + RouteError error() const; + + int count() const; + Q_INVOKABLE QDeclarativeGeoRoute *get(int index); + Q_INVOKABLE void reset(); + Q_INVOKABLE void cancel(); + +Q_SIGNALS: + void countChanged(); + void pluginChanged(); + void queryChanged(); + void autoUpdateChanged(); + void statusChanged(); + void errorChanged(); //emitted also for errorString notification + void routesChanged(); + void measurementSystemChanged(); + void abortRequested(); + +public Q_SLOTS: + void update(); + +private Q_SLOTS: + void routingFinished(QGeoRouteReply *reply); + void routingError(QGeoRouteReply *reply, + QGeoRouteReply::Error error, + const QString &errorString); + void queryDetailsChanged(); + void pluginReady(); + +private: + void setStatus(Status status); + void setError(RouteError error, const QString &errorString); + + bool complete_; + + QDeclarativeGeoServiceProvider *plugin_; + QDeclarativeGeoRouteQuery *routeQuery_; + + QList<QDeclarativeGeoRoute *> routes_; + bool autoUpdate_; + Status status_; + QString errorString_; + RouteError error_; +}; + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativeGeoRouteQuery : public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_ENUMS(TravelMode) + Q_ENUMS(FeatureType) + Q_ENUMS(FeatureWeight) + Q_ENUMS(SegmentDetail) + Q_ENUMS(ManeuverDetail) + Q_ENUMS(RouteOptimization) + Q_FLAGS(RouteOptimizations) + Q_FLAGS(ManeuverDetails) + Q_FLAGS(SegmentDetails) + Q_FLAGS(TravelModes) + + Q_PROPERTY(int numberAlternativeRoutes READ numberAlternativeRoutes WRITE setNumberAlternativeRoutes NOTIFY numberAlternativeRoutesChanged) + Q_PROPERTY(TravelModes travelModes READ travelModes WRITE setTravelModes NOTIFY travelModesChanged) + Q_PROPERTY(RouteOptimizations routeOptimizations READ routeOptimizations WRITE setRouteOptimizations NOTIFY routeOptimizationsChanged) + Q_PROPERTY(SegmentDetail segmentDetail READ segmentDetail WRITE setSegmentDetail NOTIFY segmentDetailChanged) + Q_PROPERTY(ManeuverDetail maneuverDetail READ maneuverDetail WRITE setManeuverDetail NOTIFY maneuverDetailChanged) + Q_PROPERTY(QJSValue waypoints READ waypoints WRITE setWaypoints NOTIFY waypointsChanged) + Q_PROPERTY(QJSValue excludedAreas READ excludedAreas WRITE setExcludedAreas NOTIFY excludedAreasChanged) + Q_PROPERTY(QList<int> featureTypes READ featureTypes NOTIFY featureTypesChanged) + Q_INTERFACES(QQmlParserStatus) + +public: + + explicit QDeclarativeGeoRouteQuery(QObject *parent = 0); + ~QDeclarativeGeoRouteQuery(); + + // From QQmlParserStatus + void classBegin() {} + void componentComplete(); + + QGeoRouteRequest routeRequest() const; + + enum TravelMode { + CarTravel = QGeoRouteRequest::CarTravel, + PedestrianTravel = QGeoRouteRequest::PedestrianTravel, + BicycleTravel = QGeoRouteRequest::BicycleTravel, + PublicTransitTravel = QGeoRouteRequest::PublicTransitTravel, + TruckTravel = QGeoRouteRequest::TruckTravel + }; + Q_DECLARE_FLAGS(TravelModes, TravelMode) + + enum FeatureType { + NoFeature = QGeoRouteRequest::NoFeature, + TollFeature = QGeoRouteRequest::TollFeature, + HighwayFeature = QGeoRouteRequest::HighwayFeature, + PublicTransitFeature = QGeoRouteRequest::PublicTransitFeature, + FerryFeature = QGeoRouteRequest::FerryFeature, + TunnelFeature = QGeoRouteRequest::TunnelFeature, + DirtRoadFeature = QGeoRouteRequest::DirtRoadFeature, + ParksFeature = QGeoRouteRequest::ParksFeature, + MotorPoolLaneFeature = QGeoRouteRequest::MotorPoolLaneFeature + }; + Q_DECLARE_FLAGS(FeatureTypes, FeatureType) + + enum FeatureWeight { + NeutralFeatureWeight = QGeoRouteRequest::NeutralFeatureWeight, + PreferFeatureWeight = QGeoRouteRequest::PreferFeatureWeight, + RequireFeatureWeight = QGeoRouteRequest::RequireFeatureWeight, + AvoidFeatureWeight = QGeoRouteRequest::AvoidFeatureWeight, + DisallowFeatureWeight = QGeoRouteRequest::DisallowFeatureWeight + }; + Q_DECLARE_FLAGS(FeatureWeights, FeatureWeight) + + enum RouteOptimization { + ShortestRoute = QGeoRouteRequest::ShortestRoute, + FastestRoute = QGeoRouteRequest::FastestRoute, + MostEconomicRoute = QGeoRouteRequest::MostEconomicRoute, + MostScenicRoute = QGeoRouteRequest::MostScenicRoute + }; + Q_DECLARE_FLAGS(RouteOptimizations, RouteOptimization) + + enum SegmentDetail { + NoSegmentData = 0x0000, + BasicSegmentData = 0x0001 + }; + Q_DECLARE_FLAGS(SegmentDetails, SegmentDetail) + + enum ManeuverDetail { + NoManeuvers = 0x0000, + BasicManeuvers = 0x0001 + }; + Q_DECLARE_FLAGS(ManeuverDetails, ManeuverDetail) + + void setNumberAlternativeRoutes(int numberAlternativeRoutes); + int numberAlternativeRoutes() const; + + //QList<FeatureType> featureTypes(); + QList<int> featureTypes(); + + + QJSValue waypoints(); + void setWaypoints(const QJSValue &value); + + // READ functions for list properties + QJSValue excludedAreas() const; + void setExcludedAreas(const QJSValue &value); + + Q_INVOKABLE void addWaypoint(const QGeoCoordinate &waypoint); + Q_INVOKABLE void removeWaypoint(const QGeoCoordinate &waypoint); + Q_INVOKABLE void clearWaypoints(); + + Q_INVOKABLE void addExcludedArea(const QGeoRectangle &area); + Q_INVOKABLE void removeExcludedArea(const QGeoRectangle &area); + Q_INVOKABLE void clearExcludedAreas(); + + Q_INVOKABLE void setFeatureWeight(FeatureType featureType, FeatureWeight featureWeight); + Q_INVOKABLE int featureWeight(FeatureType featureType); + Q_INVOKABLE void resetFeatureWeights(); + + /* + feature weights + */ + + void setTravelModes(TravelModes travelModes); + TravelModes travelModes() const; + + void setSegmentDetail(SegmentDetail segmentDetail); + SegmentDetail segmentDetail() const; + + void setManeuverDetail(ManeuverDetail maneuverDetail); + ManeuverDetail maneuverDetail() const; + + void setRouteOptimizations(RouteOptimizations optimization); + RouteOptimizations routeOptimizations() const; + +Q_SIGNALS: + void numberAlternativeRoutesChanged(); + void travelModesChanged(); + void routeOptimizationsChanged(); + + void waypointsChanged(); + void excludedAreasChanged(); + + void featureTypesChanged(); + void maneuverDetailChanged(); + void segmentDetailChanged(); + + void queryDetailsChanged(); + +private Q_SLOTS: + void excludedAreaCoordinateChanged(); + +private: + Q_INVOKABLE void doCoordinateChanged(); + + QGeoRouteRequest request_; + bool complete_; + bool m_excludedAreaCoordinateChanged; + +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/declarativemaps/qdeclarativegeoroutesegment.cpp b/src/location/declarativemaps/qdeclarativegeoroutesegment.cpp new file mode 100644 index 00000000..77a8a41a --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeoroutesegment.cpp @@ -0,0 +1,162 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativegeoroutesegment_p.h" + +#include <QtQml/QQmlEngine> +#include <QtQml/private/qqmlengine_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype RouteSegment + \instantiates QDeclarativeGeoRouteSegment + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-routing + \since Qt Location 5.5 + + \brief The RouteSegment type represents a segment of a Route. + + A RouteSegment instance has information about the physical layout + of the route segment, the length of the route and estimated time required + to traverse the route segment and optional RouteManeuvers associated with + the end of the route segment. + + RouteSegment instances can be thought of as edges on a routing + graph, with RouteManeuver instances as optional labels attached to the + vertices of the graph. + + The primary means of acquiring Route objects is via Routes via \l RouteModel. + + \section1 Example + + The following QML snippet demonstrates how to print information about a + route segment: + + \snippet declarative/routing.qml QtQuick import + \snippet declarative/maps.qml QtLocation import + \codeline + \snippet declarative/routing.qml RouteSegment +*/ + +QDeclarativeGeoRouteSegment::QDeclarativeGeoRouteSegment(QObject *parent) + : QObject(parent) +{ + maneuver_ = new QDeclarativeGeoManeuver(this); +} + +QDeclarativeGeoRouteSegment::QDeclarativeGeoRouteSegment(const QGeoRouteSegment &segment, + QObject *parent) + : QObject(parent), + segment_(segment) +{ + maneuver_ = new QDeclarativeGeoManeuver(segment_.maneuver(), this); +} + +QDeclarativeGeoRouteSegment::~QDeclarativeGeoRouteSegment() {} + +/*! + \qmlproperty int QtLocation::RouteSegment::travelTime + + Read-only property which holds the estimated amount of time it will take to + traverse this segment, in seconds. + +*/ + +int QDeclarativeGeoRouteSegment::travelTime() const +{ + return segment_.travelTime(); +} + +/*! + \qmlproperty real QtLocation::RouteSegment::distance + + Read-only property which holds the distance covered by this segment of the route, in meters. + +*/ + +qreal QDeclarativeGeoRouteSegment::distance() const +{ + return segment_.distance(); +} + +/*! + \qmlproperty RouteManeuver QtLocation::RouteSegment::maneuver + + Read-only property which holds the maneuver for this route segment. + + Will return invalid maneuver if no information has been attached to the endpoint + of this route segment. +*/ + +QDeclarativeGeoManeuver *QDeclarativeGeoRouteSegment::maneuver() const +{ + return maneuver_; +} + +/*! + \qmlproperty QJSValue QtLocation::RouteSegment::path + + Read-only property which holds the geographical coordinates of this segment. + Coordinates are listed in the order in which they would be traversed by someone + traveling along this segment of the route. + + To access individual segments you can use standard list accessors: 'path.length' + indicates the number of objects and 'path[index starting from zero]' gives + the actual object. + + \sa QtPositioning::coordinate +*/ + +QJSValue QDeclarativeGeoRouteSegment::path() const +{ + QQmlContext *context = QQmlEngine::contextForObject(parent()); + QQmlEngine *engine = context->engine(); + QV4::ExecutionEngine *v4 = QQmlEnginePrivate::getV4Engine(engine); + + QV4::Scope scope(v4); + QV4::Scoped<QV4::ArrayObject> pathArray(scope, v4->newArrayObject(segment_.path().length())); + for (int i = 0; i < segment_.path().length(); ++i) { + const QGeoCoordinate &c = segment_.path().at(i); + + QV4::ScopedValue cv(scope, v4->fromVariant(QVariant::fromValue(c))); + pathArray->putIndexed(i, cv); + } + + return QJSValue(v4, pathArray.asReturnedValue()); +} + +QT_END_NAMESPACE diff --git a/src/location/declarativemaps/qdeclarativegeoroutesegment_p.h b/src/location/declarativemaps/qdeclarativegeoroutesegment_p.h new file mode 100644 index 00000000..c3203ef0 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeoroutesegment_p.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEGEOROUTESEGMENT_H +#define QDECLARATIVEGEOROUTESEGMENT_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtLocation/private/qlocationglobal_p.h> +#include <QtLocation/private/qdeclarativegeomaneuver_p.h> + +#include <QtCore/QObject> +#include <QtQml/qjsvalue.h> +#include <QtLocation/QGeoRouteSegment> + +QT_BEGIN_NAMESPACE + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativeGeoRouteSegment : public QObject +{ + Q_OBJECT + + Q_PROPERTY(int travelTime READ travelTime CONSTANT) + Q_PROPERTY(qreal distance READ distance CONSTANT) + Q_PROPERTY(QJSValue path READ path CONSTANT) + Q_PROPERTY(QDeclarativeGeoManeuver *maneuver READ maneuver CONSTANT) + +public: + explicit QDeclarativeGeoRouteSegment(QObject *parent = 0); + QDeclarativeGeoRouteSegment(const QGeoRouteSegment &segment, QObject *parent = 0); + ~QDeclarativeGeoRouteSegment(); + + int travelTime() const; + qreal distance() const; + QJSValue path() const; + QDeclarativeGeoManeuver *maneuver() const; + +private: + QGeoRouteSegment segment_; + QDeclarativeGeoManeuver *maneuver_; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/location/declarativemaps/qdeclarativegeoserviceprovider.cpp b/src/location/declarativemaps/qdeclarativegeoserviceprovider.cpp new file mode 100644 index 00000000..54f70c11 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeoserviceprovider.cpp @@ -0,0 +1,819 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativegeoserviceprovider_p.h" +#include <QtQml/QQmlInfo> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype Plugin + \instantiates QDeclarativeGeoServiceProvider + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-common + \since Qt Location 5.5 + + \brief The Plugin type describes a Location based services plugin. + + The Plugin type is used to declaratively specify which available + GeoServices plugin should be used for various tasks in the Location API. + Plugins are used by \l Map, \l RouteModel, and \l GeocodeModel + types, as well as a variety of others. + + Plugins recognized by the system have a \l name property, a simple string + normally indicating the name of the service that the Plugin retrieves + data from. They also have a variety of features, which can be test for using the + \l {supportsRouting()}, \l {supportsGeocoding()}, \l {supportsMapping()} and + \l {supportsPlaces()} methods. + + When a Plugin object is created, it is "detached" and not associated with + any actual service plugin. Once it has received information via setting + its \l name, \l preferred, or \l required properties, it will choose an + appropriate service plugin to attach to. Plugin objects can only be + attached once; to use multiple plugins, create multiple Plugin objects. + + \section2 Example Usage + + The following snippet shows a Plugin object being created with the + \l required and \l preferred properties set. This Plugin will attach to the + first found plugin that supports both mapping and geocoding, and will + prefer plugins named "here" or "osm" to any others. + + \code + Plugin { + id: plugin + preferred: ["here", "osm"] + required: Plugin.AnyMappingFeatures | Plugin.AnyGeocodingFeatures + } + \endcode +*/ + +QDeclarativeGeoServiceProvider::QDeclarativeGeoServiceProvider(QObject *parent) +: QObject(parent), + sharedProvider_(0), + required_(new QDeclarativeGeoServiceProviderRequirements), + complete_(false), + experimental_(false) +{ + locales_.append(QLocale().name()); +} + +QDeclarativeGeoServiceProvider::~QDeclarativeGeoServiceProvider() +{ + delete required_; + delete sharedProvider_; +} + + + +/*! + \qmlproperty string Plugin::name + + This property holds the name of the plugin. Setting this property + will cause the Plugin to only attach to a plugin with exactly this + name. The value of \l required will be ignored. +*/ +void QDeclarativeGeoServiceProvider::setName(const QString &name) +{ + if (name_ == name) + return; + + name_ = name; + delete sharedProvider_; + sharedProvider_ = new QGeoServiceProvider(name_, parameterMap()); + sharedProvider_->setLocale(locales_.at(0)); + sharedProvider_->setAllowExperimental(experimental_); + + emit nameChanged(name_); + emit attached(); +} + +QString QDeclarativeGeoServiceProvider::name() const +{ + return name_; +} + + +/*! + \qmlproperty stringlist Plugin::availableServiceProviders + + This property holds a list of all available service plugins' names. This + can be used to manually enumerate the available plugins if the + control provided by \l name and \l required is not sufficient for your + needs. +*/ +QStringList QDeclarativeGeoServiceProvider::availableServiceProviders() +{ + return QGeoServiceProvider::availableServiceProviders(); +} + +/*! + \internal +*/ +void QDeclarativeGeoServiceProvider::componentComplete() +{ + complete_ = true; + if (!name_.isEmpty()) { + return; + } + + if (!prefer_.isEmpty() + || required_->mappingRequirements() != NoMappingFeatures + || required_->routingRequirements() != NoRoutingFeatures + || required_->geocodingRequirements() != NoGeocodingFeatures + || required_->placesRequirements() != NoPlacesFeatures) { + + QStringList providers = QGeoServiceProvider::availableServiceProviders(); + + /* first check any preferred plugins */ + foreach (const QString &name, prefer_) { + if (providers.contains(name)) { + // so we don't try it again later + providers.removeAll(name); + + QGeoServiceProvider sp(name, parameterMap(), experimental_); + if (required_->matches(&sp)) { + setName(name); + return; + } + } + } + + /* then try the rest */ + foreach (const QString &name, providers) { + QGeoServiceProvider sp(name, parameterMap(), experimental_); + if (required_->matches(&sp)) { + setName(name); + return; + } + } + + qmlWarning(this) << "Could not find a plugin with the required features to attach to"; + } +} + +/*! + \qmlmethod bool Plugin::supportsGeocoding(GeocodingFeatures features) + + This method returns a boolean indicating whether the specified set of \a features are supported + by the geo service provider plugin. True is returned if all specified \a features are + supported; otherwise false is returned. + + The \a features parameter can be any flag combination of: + \table + \header + \li Feature + \li Description + \row + \li Plugin.NoGeocodingFeatures + \li No geocoding features are supported. + \row + \li Plugin.OnlineGeocodingFeature + \li Online geocoding is supported. + \row + \li Plugin.OfflineGeocodingFeature + \li Offline geocoding is supported. + \row + \li Plugin.ReverseGeocodingFeature + \li Reverse geocoding is supported. + \row + \li Plugin.LocalizedGeocodingFeature + \li Supports returning geocoding results with localized addresses. + \row + \li Plugin.AnyGeocodingFeatures + \li Matches a geo service provider that provides any geocoding features. + \endtable +*/ +bool QDeclarativeGeoServiceProvider::supportsGeocoding(const GeocodingFeatures &feature) const +{ + QGeoServiceProvider *sp = sharedGeoServiceProvider(); + QGeoServiceProvider::GeocodingFeatures f = + static_cast<QGeoServiceProvider::GeocodingFeature>(int(feature)); + if (f == QGeoServiceProvider::AnyGeocodingFeatures) + return (sp && (sp->geocodingFeatures() != QGeoServiceProvider::NoGeocodingFeatures)); + else + return (sp && (sp->geocodingFeatures() & f) == f); +} + +/*! + \qmlmethod bool Plugin::supportsMapping(MappingFeatures features) + + This method returns a boolean indicating whether the specified set of \a features are supported + by the geo service provider plugin. True is returned if all specified \a features are + supported; otherwise false is returned. + + The \a features parameter can be any flag combination of: + \table + \header + \li Feature + \li Description + \row + \li Plugin.NoMappingFeatures + \li No mapping features are supported. + \row + \li Plugin.OnlineMappingFeature + \li Online mapping is supported. + \row + \li Plugin.OfflineMappingFeature + \li Offline mapping is supported. + \row + \li Plugin.LocalizedMappingFeature + \li Supports returning localized map data. + \row + \li Plugin.AnyMappingFeatures + \li Matches a geo service provider that provides any mapping features. + \endtable +*/ +bool QDeclarativeGeoServiceProvider::supportsMapping(const MappingFeatures &feature) const +{ + QGeoServiceProvider *sp = sharedGeoServiceProvider(); + QGeoServiceProvider::MappingFeatures f = + static_cast<QGeoServiceProvider::MappingFeature>(int(feature)); + if (f == QGeoServiceProvider::AnyMappingFeatures) + return (sp && (sp->mappingFeatures() != QGeoServiceProvider::NoMappingFeatures)); + else + return (sp && (sp->mappingFeatures() & f) == f); +} + +/*! + \qmlmethod bool Plugin::supportsRouting(RoutingFeatures features) + + This method returns a boolean indicating whether the specified set of \a features are supported + by the geo service provider plugin. True is returned if all specified \a features are + supported; otherwise false is returned. + + The \a features parameter can be any flag combination of: + \table + \header + \li Feature + \li Description + \row + \li Plugin.NoRoutingFeatures + \li No routing features are supported. + \row + \li Plugin.OnlineRoutingFeature + \li Online routing is supported. + \row + \li Plugin.OfflineRoutingFeature + \li Offline routing is supported. + \row + \li Plugin.LocalizedRoutingFeature + \li Supports returning routes with localized addresses and instructions. + \row + \li Plugin.RouteUpdatesFeature + \li Updating an existing route based on the current position is supported. + \row + \li Plugin.AlternativeRoutesFeature + \li Supports returning alternative routes. + \row + \li Plugin.ExcludeAreasRoutingFeature + \li Supports specifying a areas which the returned route must not cross. + \row + \li Plugin.AnyRoutingFeatures + \li Matches a geo service provider that provides any routing features. + \endtable +*/ +bool QDeclarativeGeoServiceProvider::supportsRouting(const RoutingFeatures &feature) const +{ + QGeoServiceProvider *sp = sharedGeoServiceProvider(); + QGeoServiceProvider::RoutingFeatures f = + static_cast<QGeoServiceProvider::RoutingFeature>(int(feature)); + if (f == QGeoServiceProvider::AnyRoutingFeatures) + return (sp && (sp->routingFeatures() != QGeoServiceProvider::NoRoutingFeatures)); + else + return (sp && (sp->routingFeatures() & f) == f); +} + +/*! + \qmlmethod bool Plugin::supportsPlaces(PlacesFeatures features) + + This method returns a boolean indicating whether the specified set of \a features are supported + by the geo service provider plugin. True is returned if all specified \a features are + supported; otherwise false is returned. + + The \a features parameter can be any flag combination of: + \table + \header + \li Feature + \li Description + \row + \li Plugin.NoPlacesFeatures + \li No places features are supported. + \row + \li Plugin.OnlinePlacesFeature + \li Online places is supported. + \row + \li Plugin.OfflinePlacesFeature + \li Offline places is supported. + \row + \li Plugin.SavePlaceFeature + \li Saving categories is supported. + \row + \li Plugin.RemovePlaceFeature + \li Removing or deleting places is supported. + \row + \li Plugin.PlaceRecommendationsFeature + \li Searching for recommended places similar to another place is supported. + \row + \li Plugin.SearchSuggestionsFeature + \li Search suggestions is supported. + \row + \li Plugin.LocalizedPlacesFeature + \li Supports returning localized place data. + \row + \li Plugin.NotificationsFeature + \li Notifications of place and category changes is supported. + \row + \li Plugin.PlaceMatchingFeature + \li Supports matching places from two different geo service providers. + \row + \li Plugin.AnyPlacesFeatures + \li Matches a geo service provider that provides any places features. + \endtable +*/ +bool QDeclarativeGeoServiceProvider::supportsPlaces(const PlacesFeatures &features) const +{ + QGeoServiceProvider *sp = sharedGeoServiceProvider(); + QGeoServiceProvider::PlacesFeatures f = + static_cast<QGeoServiceProvider::PlacesFeature>(int(features)); + if (f == QGeoServiceProvider::AnyPlacesFeatures) + return (sp && (sp->placesFeatures() != QGeoServiceProvider::NoPlacesFeatures)); + else + return (sp && (sp->placesFeatures() & f) == f); +} + +/*! + \qmlproperty enumeration Plugin::required + + This property contains the set of features that will be required by the + Plugin object when choosing which service plugin to attach to. If the + \l name property is set, this has no effect. + + Any of the following values or a bitwise combination of multiple values + may be set: + + \list + \li Plugin.NoFeatures + \li Plugin.GeocodingFeature + \li Plugin.ReverseGeocodingFeature + \li Plugin.RoutingFeature + \li Plugin.MappingFeature + \li Plugin.AnyPlacesFeature + \endlist +*/ +QDeclarativeGeoServiceProviderRequirements *QDeclarativeGeoServiceProvider::requirements() const +{ + return required_; +} + +/*! + \qmlproperty stringlist Plugin::preferred + + This property contains an ordered list of preferred plugin names, which + will be checked for the required features set in \l{Plugin::required}{required} + before any other available plugins are checked. +*/ +QStringList QDeclarativeGeoServiceProvider::preferred() const +{ + return prefer_; +} + +void QDeclarativeGeoServiceProvider::setPreferred(const QStringList &val) +{ + prefer_ = val; + emit preferredChanged(prefer_); +} + +/*! + \qmlproperty bool Plugin::isAttached + + This property indicates if the Plugin item is attached to a geoservice provider plugin. +*/ +bool QDeclarativeGeoServiceProvider::isAttached() const +{ + return (sharedProvider_ != 0); +} + +/*! + \qmlproperty bool Plugin::allowExperimental + + This property indicates if experimental plugins can be used. +*/ +bool QDeclarativeGeoServiceProvider::allowExperimental() const +{ + return experimental_; +} + +void QDeclarativeGeoServiceProvider::setAllowExperimental(bool allow) +{ + if (experimental_ == allow) + return; + + experimental_ = allow; + if (sharedProvider_) + sharedProvider_->setAllowExperimental(allow); + + emit allowExperimentalChanged(allow); +} + +/*! + \internal +*/ +QGeoServiceProvider *QDeclarativeGeoServiceProvider::sharedGeoServiceProvider() const +{ + return sharedProvider_; +} + +/*! + \qmlproperty stringlist Plugin::locales + + This property contains an ordered list of preferred plugin locales. If the first locale cannot be accommodated, then + the backend falls back to using the second, and so on. By default the locales property contains the system locale. + + The locales are specified as strings which have the format + "language[_script][_country]" or "C", where: + + \list + \li language is a lowercase, two-letter, ISO 639 language code, + \li script is a titlecase, four-letter, ISO 15924 script code, + \li country is an uppercase, two- or three-letter, ISO 3166 country code (also "419" as defined by United Nations), + \li the "C" locale is identical in behavior to English/UnitedStates as per QLocale + \endlist + + If the first specified locale cannot be accommodated, the \l {Plugin} falls back to the next and so forth. + Some \l {Plugin} backends may not support a set of locales which are rigidly defined. An arbitrary + example is that some \l {Place}'s in France could have French and English localizations, while + certain areas in America may only have the English localization available. In the above scenario, + the set of supported locales is context dependent on the search location. + + If the \l {Plugin} cannot accommodate any of the preferred locales, the manager falls + back to using a supported language that is backend specific. + + For \l {Plugin}'s that do not support locales, the locales list is always empty. + + The following code demonstrates how to set a single and multiple locales: + \snippet declarative/plugin.qml Plugin locale +*/ +QStringList QDeclarativeGeoServiceProvider::locales() const +{ + return locales_; +} + +void QDeclarativeGeoServiceProvider::setLocales(const QStringList &locales) +{ + if (locales_ == locales) + return; + + locales_ = locales; + + if (locales_.isEmpty()) + locales_.append(QLocale().name()); + + if (sharedProvider_) + sharedProvider_->setLocale(locales_.at(0)); + + emit localesChanged(); +} + +/*! + \qmlproperty list<PluginParameter> Plugin::parameters + \default + + This property holds the list of plugin parameters. +*/ +QQmlListProperty<QDeclarativeGeoServiceProviderParameter> QDeclarativeGeoServiceProvider::parameters() +{ + return QQmlListProperty<QDeclarativeGeoServiceProviderParameter>(this, + 0, + parameter_append, + parameter_count, + parameter_at, + parameter_clear); +} + +/*! + \internal +*/ +void QDeclarativeGeoServiceProvider::parameter_append(QQmlListProperty<QDeclarativeGeoServiceProviderParameter> *prop, QDeclarativeGeoServiceProviderParameter *parameter) +{ + QDeclarativeGeoServiceProvider *p = static_cast<QDeclarativeGeoServiceProvider *>(prop->object); + p->parameters_.append(parameter); + if (p->sharedProvider_) + p->sharedProvider_->setParameters(p->parameterMap()); +} + +/*! + \internal +*/ +int QDeclarativeGeoServiceProvider::parameter_count(QQmlListProperty<QDeclarativeGeoServiceProviderParameter> *prop) +{ + return static_cast<QDeclarativeGeoServiceProvider *>(prop->object)->parameters_.count(); +} + +/*! + \internal +*/ +QDeclarativeGeoServiceProviderParameter *QDeclarativeGeoServiceProvider::parameter_at(QQmlListProperty<QDeclarativeGeoServiceProviderParameter> *prop, int index) +{ + return static_cast<QDeclarativeGeoServiceProvider *>(prop->object)->parameters_[index]; +} + +/*! + \internal +*/ +void QDeclarativeGeoServiceProvider::parameter_clear(QQmlListProperty<QDeclarativeGeoServiceProviderParameter> *prop) +{ + QDeclarativeGeoServiceProvider *p = static_cast<QDeclarativeGeoServiceProvider *>(prop->object); + p->parameters_.clear(); + if (p->sharedProvider_) + p->sharedProvider_->setParameters(p->parameterMap()); +} + +/*! + \internal +*/ +QVariantMap QDeclarativeGeoServiceProvider::parameterMap() const +{ + QVariantMap map; + + for (int i = 0; i < parameters_.size(); ++i) { + QDeclarativeGeoServiceProviderParameter *parameter = parameters_.at(i); + map.insert(parameter->name(), parameter->value()); + } + + return map; +} + +/******************************************************************************* +*******************************************************************************/ + +QDeclarativeGeoServiceProviderRequirements::QDeclarativeGeoServiceProviderRequirements(QObject *parent) + : QObject(parent), + mapping_(QDeclarativeGeoServiceProvider::NoMappingFeatures), + routing_(QDeclarativeGeoServiceProvider::NoRoutingFeatures), + geocoding_(QDeclarativeGeoServiceProvider::NoGeocodingFeatures), + places_(QDeclarativeGeoServiceProvider::NoPlacesFeatures) +{ +} + +QDeclarativeGeoServiceProviderRequirements::~QDeclarativeGeoServiceProviderRequirements() +{ +} + +/*! + \internal +*/ +QDeclarativeGeoServiceProvider::MappingFeatures QDeclarativeGeoServiceProviderRequirements::mappingRequirements() const +{ + return mapping_; +} + +/*! + \internal +*/ +void QDeclarativeGeoServiceProviderRequirements::setMappingRequirements(const QDeclarativeGeoServiceProvider::MappingFeatures &features) +{ + if (mapping_ == features) + return; + + mapping_ = features; + emit mappingRequirementsChanged(mapping_); + emit requirementsChanged(); +} + +/*! + \internal +*/ +QDeclarativeGeoServiceProvider::RoutingFeatures QDeclarativeGeoServiceProviderRequirements::routingRequirements() const +{ + return routing_; +} + +/*! + \internal +*/ +void QDeclarativeGeoServiceProviderRequirements::setRoutingRequirements(const QDeclarativeGeoServiceProvider::RoutingFeatures &features) +{ + if (routing_ == features) + return; + + routing_ = features; + emit routingRequirementsChanged(routing_); + emit requirementsChanged(); +} + +/*! + \internal +*/ +QDeclarativeGeoServiceProvider::GeocodingFeatures QDeclarativeGeoServiceProviderRequirements::geocodingRequirements() const +{ + return geocoding_; +} + +/*! + \internal +*/ +void QDeclarativeGeoServiceProviderRequirements::setGeocodingRequirements(const QDeclarativeGeoServiceProvider::GeocodingFeatures &features) +{ + if (geocoding_ == features) + return; + + geocoding_ = features; + emit geocodingRequirementsChanged(geocoding_); + emit requirementsChanged(); +} + +/*! + \internal + + */ +QDeclarativeGeoServiceProvider::PlacesFeatures QDeclarativeGeoServiceProviderRequirements::placesRequirements() const +{ + return places_; +} + +/*! + \internal +*/ +void QDeclarativeGeoServiceProviderRequirements::setPlacesRequirements(const QDeclarativeGeoServiceProvider::PlacesFeatures &features) +{ + if (places_ == features) + return; + + places_ = features; + emit placesRequirementsChanged(places_); + emit requirementsChanged(); +} + +/*! + \internal +*/ +bool QDeclarativeGeoServiceProviderRequirements::matches(const QGeoServiceProvider *provider) const +{ + QGeoServiceProvider::MappingFeatures mapping = + static_cast<QGeoServiceProvider::MappingFeatures>(int(mapping_)); + + // extra curlies here to avoid "dangling" else, which could belong to either if + // same goes for all the rest of these blocks + if (mapping == QGeoServiceProvider::AnyMappingFeatures) { + if (provider->mappingFeatures() == QGeoServiceProvider::NoMappingFeatures) + return false; + } else { + if ((provider->mappingFeatures() & mapping) != mapping) + return false; + } + + QGeoServiceProvider::RoutingFeatures routing = + static_cast<QGeoServiceProvider::RoutingFeatures>(int(routing_)); + + if (routing == QGeoServiceProvider::AnyRoutingFeatures) { + if (provider->routingFeatures() == QGeoServiceProvider::NoRoutingFeatures) + return false; + } else { + if ((provider->routingFeatures() & routing) != routing) + return false; + } + + QGeoServiceProvider::GeocodingFeatures geocoding = + static_cast<QGeoServiceProvider::GeocodingFeatures>(int(geocoding_)); + + if (geocoding == QGeoServiceProvider::AnyGeocodingFeatures) { + if (provider->geocodingFeatures() == QGeoServiceProvider::NoGeocodingFeatures) + return false; + } else { + if ((provider->geocodingFeatures() & geocoding) != geocoding) + return false; + } + + QGeoServiceProvider::PlacesFeatures places = + static_cast<QGeoServiceProvider::PlacesFeatures>(int(places_)); + + if (places == QGeoServiceProvider::AnyPlacesFeatures) { + if (provider->placesFeatures() == QGeoServiceProvider::NoPlacesFeatures) + return false; + } else { + if ((provider->placesFeatures() & places) != places) + return false; + } + + return true; +} + +/******************************************************************************* +*******************************************************************************/ + +/*! + \qmltype PluginParameter + \instantiates QDeclarativeGeoServiceProviderParameter + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-common + \since Qt Location 5.5 + + \brief The PluginParameter type describes a parameter to a \l Plugin. + + The PluginParameter object is used to provide a parameter of some kind + to a Plugin. Typically these parameters contain details like an application + token for access to a service, or a proxy server to use for network access. + + To set such a parameter, declare a PluginParameter inside a \l Plugin + object, and give it \l{name} and \l{value} properties. A list of valid + parameter names for each plugin is available from the + \l {Qt Location#Plugin References and Parameters}{plugin reference pages}. + + \section2 Example Usage + + The following example shows an instantiation of the \l {Qt Location HERE Plugin}{HERE} plugin + with a mapping API \e app_id and \e token pair specific to the application. + + \code + Plugin { + name: "here" + PluginParameter { name: "here.app_id"; value: "EXAMPLE_API_ID" } + PluginParameter { name: "here.token"; value: "EXAMPLE_TOKEN_123" } + } + \endcode +*/ + +QDeclarativeGeoServiceProviderParameter::QDeclarativeGeoServiceProviderParameter(QObject *parent) + : QObject(parent) {} + +QDeclarativeGeoServiceProviderParameter::~QDeclarativeGeoServiceProviderParameter() {} + +/*! + \qmlproperty string PluginParameter::name + + This property holds the name of the plugin parameter as a single formatted string. +*/ +void QDeclarativeGeoServiceProviderParameter::setName(const QString &name) +{ + if (name_ == name) + return; + + name_ = name; + + emit nameChanged(name_); +} + +QString QDeclarativeGeoServiceProviderParameter::name() const +{ + return name_; +} + +/*! + \qmlproperty QVariant PluginParameter::value + + This property holds the value of the plugin parameter which support different types of values (variant). +*/ +void QDeclarativeGeoServiceProviderParameter::setValue(const QVariant &value) +{ + if (value_ == value) + return; + + value_ = value; + + emit valueChanged(value_); +} + +QVariant QDeclarativeGeoServiceProviderParameter::value() const +{ + return value_; +} + +/******************************************************************************* +*******************************************************************************/ + +QT_END_NAMESPACE + diff --git a/src/location/declarativemaps/qdeclarativegeoserviceprovider_p.h b/src/location/declarativemaps/qdeclarativegeoserviceprovider_p.h new file mode 100644 index 00000000..bcf67124 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativegeoserviceprovider_p.h @@ -0,0 +1,284 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEQGEOSERVICEPROVIDER_H +#define QDECLARATIVEQGEOSERVICEPROVIDER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtLocation/private/qlocationglobal_p.h> + +#include <QtCore/QMap> +#include <QtCore/QString> +#include <QtCore/QStringList> +#include <QtCore/QVariant> +#include <QtQml/qqml.h> +#include <QtQml/QQmlParserStatus> +#include <QtQml/QQmlListProperty> +#include <QtLocation/QGeoServiceProvider> + +QT_BEGIN_NAMESPACE + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativeGeoServiceProviderParameter : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) + Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged) + +public: + explicit QDeclarativeGeoServiceProviderParameter(QObject *parent = 0); + ~QDeclarativeGeoServiceProviderParameter(); + + void setName(const QString &name); + QString name() const; + + void setValue(const QVariant &value); + QVariant value() const; + +Q_SIGNALS: + void nameChanged(const QString &name); + void valueChanged(const QVariant &value); + +private: + QString name_; + QVariant value_; +}; + +class QDeclarativeGeoServiceProviderRequirements; + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativeGeoServiceProvider : public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_ENUMS(RoutingFeature) + Q_ENUMS(GeocodingFeature) + Q_ENUMS(MappingFeature) + Q_ENUMS(PlacesFeature) + + Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) + Q_PROPERTY(QStringList availableServiceProviders READ availableServiceProviders CONSTANT) + Q_PROPERTY(QQmlListProperty<QDeclarativeGeoServiceProviderParameter> parameters READ parameters) + Q_PROPERTY(QDeclarativeGeoServiceProviderRequirements *required READ requirements) + Q_PROPERTY(QStringList locales READ locales WRITE setLocales NOTIFY localesChanged) + Q_PROPERTY(QStringList preferred READ preferred WRITE setPreferred NOTIFY preferredChanged) + Q_PROPERTY(bool allowExperimental READ allowExperimental WRITE setAllowExperimental NOTIFY allowExperimentalChanged) + Q_PROPERTY(bool isAttached READ isAttached NOTIFY attached) + + Q_CLASSINFO("DefaultProperty", "parameters") + Q_INTERFACES(QQmlParserStatus) + +public: + explicit QDeclarativeGeoServiceProvider(QObject *parent = Q_NULLPTR); + ~QDeclarativeGeoServiceProvider(); + + enum RoutingFeature { + NoRoutingFeatures = QGeoServiceProvider::NoRoutingFeatures, + OnlineRoutingFeature = QGeoServiceProvider::OnlineRoutingFeature, + OfflineRoutingFeature = QGeoServiceProvider::OfflineRoutingFeature, + LocalizedRoutingFeature = QGeoServiceProvider::LocalizedRoutingFeature, + RouteUpdatesFeature = QGeoServiceProvider::RouteUpdatesFeature, + AlternativeRoutesFeature = QGeoServiceProvider::AlternativeRoutesFeature, + ExcludeAreasRoutingFeature = QGeoServiceProvider::ExcludeAreasRoutingFeature, + AnyRoutingFeatures = QGeoServiceProvider::AnyRoutingFeatures + }; + + enum GeocodingFeature { + NoGeocodingFeatures = QGeoServiceProvider::NoGeocodingFeatures, + OnlineGeocodingFeature = QGeoServiceProvider::OnlineGeocodingFeature, + OfflineGeocodingFeature = QGeoServiceProvider::OfflineGeocodingFeature, + ReverseGeocodingFeature = QGeoServiceProvider::ReverseGeocodingFeature, + LocalizedGeocodingFeature = QGeoServiceProvider::LocalizedGeocodingFeature, + AnyGeocodingFeatures = QGeoServiceProvider::AnyGeocodingFeatures + }; + + enum MappingFeature { + NoMappingFeatures = QGeoServiceProvider::NoMappingFeatures, + OnlineMappingFeature = QGeoServiceProvider::OnlineMappingFeature, + OfflineMappingFeature = QGeoServiceProvider::OfflineMappingFeature, + LocalizedMappingFeature = QGeoServiceProvider::LocalizedMappingFeature, + AnyMappingFeatures = QGeoServiceProvider::AnyMappingFeatures + }; + + enum PlacesFeature { + NoPlacesFeatures = QGeoServiceProvider::NoPlacesFeatures, + OnlinePlacesFeature = QGeoServiceProvider::OnlinePlacesFeature, + OfflinePlacesFeature = QGeoServiceProvider::OfflinePlacesFeature, + SavePlaceFeature = QGeoServiceProvider::SavePlaceFeature, + RemovePlaceFeature = QGeoServiceProvider::RemovePlaceFeature, + SaveCategoryFeature = QGeoServiceProvider::SaveCategoryFeature, + RemoveCategoryFeature = QGeoServiceProvider::RemoveCategoryFeature, + PlaceRecommendationsFeature = QGeoServiceProvider::PlaceRecommendationsFeature, + SearchSuggestionsFeature = QGeoServiceProvider::SearchSuggestionsFeature, + LocalizedPlacesFeature = QGeoServiceProvider::LocalizedPlacesFeature, + NotificationsFeature = QGeoServiceProvider::NotificationsFeature, + PlaceMatchingFeature = QGeoServiceProvider::PlaceMatchingFeature, + AnyPlacesFeatures = QGeoServiceProvider::AnyPlacesFeatures + }; + + Q_DECLARE_FLAGS(RoutingFeatures, RoutingFeature) + Q_FLAGS(RoutingFeatures) + + Q_DECLARE_FLAGS(GeocodingFeatures, GeocodingFeature) + Q_FLAGS(GeocodingFeatures) + + Q_DECLARE_FLAGS(MappingFeatures, MappingFeature) + Q_FLAGS(MappingFeatures) + + Q_DECLARE_FLAGS(PlacesFeatures, PlacesFeature) + Q_FLAGS(PlacesFeatures) + + // From QQmlParserStatus + virtual void classBegin() {} + virtual void componentComplete(); + + void setName(const QString &name); + QString name() const; + + QQmlListProperty<QDeclarativeGeoServiceProviderParameter> parameters(); + QVariantMap parameterMap() const; + + QStringList availableServiceProviders(); + + QDeclarativeGeoServiceProviderRequirements *requirements() const; + + QStringList preferred() const; + void setPreferred(const QStringList &val); + + QGeoServiceProvider *sharedGeoServiceProvider() const; + + Q_INVOKABLE bool supportsRouting(const RoutingFeatures &feature = AnyRoutingFeatures) const; + Q_INVOKABLE bool supportsGeocoding(const GeocodingFeatures &feature = AnyGeocodingFeatures) const; + Q_INVOKABLE bool supportsMapping(const MappingFeatures &feature = AnyMappingFeatures) const; + Q_INVOKABLE bool supportsPlaces(const PlacesFeatures &feature = AnyPlacesFeatures) const; + + QStringList locales() const; + void setLocales(const QStringList &locales); + + bool isAttached() const; + + void setAllowExperimental(bool allow); + bool allowExperimental() const; + +Q_SIGNALS: + void nameChanged(const QString &name); + void localesChanged(); + void attached(); + void preferredChanged(const QStringList &preferences); + void allowExperimentalChanged(bool allow); + +private: + static void parameter_append(QQmlListProperty<QDeclarativeGeoServiceProviderParameter> *prop, QDeclarativeGeoServiceProviderParameter *mapObject); + static int parameter_count(QQmlListProperty<QDeclarativeGeoServiceProviderParameter> *prop); + static QDeclarativeGeoServiceProviderParameter *parameter_at(QQmlListProperty<QDeclarativeGeoServiceProviderParameter> *prop, int index); + static void parameter_clear(QQmlListProperty<QDeclarativeGeoServiceProviderParameter> *prop); + + QGeoServiceProvider *sharedProvider_; + QString name_; + QList<QDeclarativeGeoServiceProviderParameter *> parameters_; + QDeclarativeGeoServiceProviderRequirements *required_; + bool complete_; + bool experimental_; + QStringList locales_; + QStringList prefer_; + Q_DISABLE_COPY(QDeclarativeGeoServiceProvider) +}; + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativeGeoServiceProviderRequirements : public QObject +{ + Q_OBJECT + Q_PROPERTY(QDeclarativeGeoServiceProvider::MappingFeatures mapping + READ mappingRequirements WRITE setMappingRequirements + NOTIFY mappingRequirementsChanged) + Q_PROPERTY(QDeclarativeGeoServiceProvider::RoutingFeatures routing + READ routingRequirements WRITE setRoutingRequirements + NOTIFY routingRequirementsChanged) + Q_PROPERTY(QDeclarativeGeoServiceProvider::GeocodingFeatures geocoding + READ geocodingRequirements WRITE setGeocodingRequirements + NOTIFY geocodingRequirementsChanged) + Q_PROPERTY(QDeclarativeGeoServiceProvider::PlacesFeatures places + READ placesRequirements WRITE setPlacesRequirements + NOTIFY placesRequirementsChanged) + +public: + explicit QDeclarativeGeoServiceProviderRequirements(QObject *parent = 0); + ~QDeclarativeGeoServiceProviderRequirements(); + + QDeclarativeGeoServiceProvider::MappingFeatures mappingRequirements() const; + void setMappingRequirements(const QDeclarativeGeoServiceProvider::MappingFeatures &features); + + QDeclarativeGeoServiceProvider::RoutingFeatures routingRequirements() const; + void setRoutingRequirements(const QDeclarativeGeoServiceProvider::RoutingFeatures &features); + + QDeclarativeGeoServiceProvider::GeocodingFeatures geocodingRequirements() const; + void setGeocodingRequirements(const QDeclarativeGeoServiceProvider::GeocodingFeatures &features); + + QDeclarativeGeoServiceProvider::PlacesFeatures placesRequirements() const; + void setPlacesRequirements(const QDeclarativeGeoServiceProvider::PlacesFeatures &features); + + Q_INVOKABLE bool matches(const QGeoServiceProvider *provider) const; + +Q_SIGNALS: + void mappingRequirementsChanged(const QDeclarativeGeoServiceProvider::MappingFeatures &features); + void routingRequirementsChanged(const QDeclarativeGeoServiceProvider::RoutingFeatures &features); + void geocodingRequirementsChanged(const QDeclarativeGeoServiceProvider::GeocodingFeatures &features); + void placesRequirementsChanged(const QDeclarativeGeoServiceProvider::PlacesFeatures &features); + + void requirementsChanged(); + +private: + QDeclarativeGeoServiceProvider::MappingFeatures mapping_; + QDeclarativeGeoServiceProvider::RoutingFeatures routing_; + QDeclarativeGeoServiceProvider::GeocodingFeatures geocoding_; + QDeclarativeGeoServiceProvider::PlacesFeatures places_; + +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativeGeoServiceProviderParameter) +QML_DECLARE_TYPE(QDeclarativeGeoServiceProviderRequirements) +QML_DECLARE_TYPE(QDeclarativeGeoServiceProvider) + +#endif diff --git a/src/location/declarativemaps/qdeclarativepolygonmapitem.cpp b/src/location/declarativemaps/qdeclarativepolygonmapitem.cpp new file mode 100644 index 00000000..0b93a294 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativepolygonmapitem.cpp @@ -0,0 +1,741 @@ +/**************************************************************************** + ** + ** Copyright (C) 2015 The Qt Company Ltd. + ** Contact: http://www.qt.io/licensing/ + ** + ** This file is part of the QtLocation module of the Qt Toolkit. + ** + ** $QT_BEGIN_LICENSE:LGPL3$ + ** 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 http://www.qt.io/terms-conditions. For further + ** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free + ** Software Foundation and appearing in the file LICENSE.GPL included in + ** the packaging of this file. Please review the following information to + ** ensure the GNU General Public License version 2.0 requirements will be + ** met: http://www.gnu.org/licenses/gpl-2.0.html. + ** + ** $QT_END_LICENSE$ + ** + ****************************************************************************/ + +#include "qdeclarativepolygonmapitem_p.h" +#include "qlocationutils_p.h" +#include "error_messages.h" +#include "locationvaluetypehelper_p.h" +#include <QtLocation/private/qgeomap_p.h> + +#include <QtCore/QScopedValueRollback> +#include <QtGui/private/qtriangulator_p.h> +#include <QtQml/QQmlInfo> +#include <QtQml/private/qqmlengine_p.h> +#include <QPainter> +#include <QPainterPath> +#include <qnumeric.h> + +#include <QtPositioning/private/qdoublevector2d_p.h> +#include <QtPositioning/private/qclipperutils_p.h> + +/* poly2tri triangulator includes */ +#include <clip2tri.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype MapPolygon + \instantiates QDeclarativePolygonMapItem + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-maps + \since Qt Location 5.5 + + \brief The MapPolygon type displays a polygon on a Map + + The MapPolygon type displays a polygon on a Map, specified in terms of an ordered list of + \l {QtPositioning::coordinate}{coordinates}. For best appearance and results, polygons should be + simple (not self-intersecting). + + The \l {QtPositioning::coordinate}{coordinates} on the path cannot be directly changed after + being added to the Polygon. Instead, copy the \l path into a var, modify the copy and reassign + the copy back to the \l path. + + \code + var path = mapPolygon.path; + path[0].latitude = 5; + mapPolygon.path = path; + \endcode + + Coordinates can also be added and removed at any time using the \l addCoordinate and + \l removeCoordinate methods. + + For drawing rectangles with "straight" edges (same latitude across one + edge, same latitude across the other), the \l MapRectangle type provides + a simpler, two-point API. + + By default, the polygon is displayed as a 1 pixel black border with no + fill. To change its appearance, use the \l color, \l border.color and + \l border.width properties. + + \note Since MapPolygons are geographic items, dragging a MapPolygon + (through the use of \l MouseArea) causes its vertices to be + recalculated in the geographic coordinate space. The edges retain the + same geographic lengths (latitude and longitude differences between the + vertices), but they remain straight. Apparent stretching of the item occurs + when dragged to a different latitude. + + \section2 Performance + + MapPolygons have a rendering cost that is O(n) with respect to the number + of vertices. This means that the per frame cost of having a Polygon on the + Map grows in direct proportion to the number of points on the Polygon. There + is an additional triangulation cost (approximately O(n log n)) which is + currently paid with each frame, but in future may be paid only upon adding + or removing points. + + Like the other map objects, MapPolygon is normally drawn without a smooth + appearance. Setting the \l {Item::opacity}{opacity} property will force the object to + be blended, which decreases performance considerably depending on the hardware in use. + + \section2 Example Usage + + The following snippet shows a MapPolygon being used to display a triangle, + with three vertices near Brisbane, Australia. The triangle is filled in + green, with a 1 pixel black border. + + \code + Map { + MapPolygon { + color: 'green' + path: [ + { latitude: -27, longitude: 153.0 }, + { latitude: -27, longitude: 154.1 }, + { latitude: -28, longitude: 153.5 } + ] + } + } + \endcode + + \image api-mappolygon.png +*/ + +QGeoMapPolygonGeometry::QGeoMapPolygonGeometry() +: assumeSimple_(false) +{ +} + +/*! + \internal +*/ +void QGeoMapPolygonGeometry::updateSourcePoints(const QGeoMap &map, + const QList<QDoubleVector2D> &path) +{ + if (!sourceDirty_) + return; + + srcPath_ = QPainterPath(); + + // build the actual path + // The approach is the same as described in QGeoMapPolylineGeometry::updateSourcePoints + srcOrigin_ = geoLeftBound_; + double unwrapBelowX = 0; + QDoubleVector2D leftBoundWrapped = map.geoProjection().wrapMapProjection(map.geoProjection().geoToMapProjection(geoLeftBound_)); + if (preserveGeometry_) + unwrapBelowX = leftBoundWrapped.x(); + + QList<QDoubleVector2D> wrappedPath; + wrappedPath.reserve(path.size()); + QDoubleVector2D wrappedLeftBound(qInf(), qInf()); + // 1) + for (int i = 0; i < path.size(); ++i) { + const QDoubleVector2D &coord = path.at(i); + QDoubleVector2D wrappedProjection = map.geoProjection().wrapMapProjection(coord); + + // We can get NaN if the map isn't set up correctly, or the projection + // is faulty -- probably best thing to do is abort + if (!qIsFinite(wrappedProjection.x()) || !qIsFinite(wrappedProjection.y())) + return; + + const bool isPointLessThanUnwrapBelowX = (wrappedProjection.x() < leftBoundWrapped.x()); + // unwrap x to preserve geometry if moved to border of map + if (preserveGeometry_ && isPointLessThanUnwrapBelowX) { + double distance = wrappedProjection.x() - unwrapBelowX; + if (distance < 0.0) + distance += 1.0; + wrappedProjection.setX(unwrapBelowX + distance); + } + if (wrappedProjection.x() < wrappedLeftBound.x() || (wrappedProjection.x() == wrappedLeftBound.x() && wrappedProjection.y() < wrappedLeftBound.y())) { + wrappedLeftBound = wrappedProjection; + } + wrappedPath.append(wrappedProjection); + } + + // 2) + QList<QList<QDoubleVector2D> > clippedPaths; + const QList<QDoubleVector2D> &visibleRegion = map.geoProjection().visibleRegion(); + if (visibleRegion.size()) { + c2t::clip2tri clipper; + clipper.addSubjectPath(QClipperUtils::qListToPath(wrappedPath), true); + clipper.addClipPolygon(QClipperUtils::qListToPath(visibleRegion)); + Paths res = clipper.execute(c2t::clip2tri::Intersection, QtClipperLib::pftEvenOdd, QtClipperLib::pftEvenOdd); + clippedPaths = QClipperUtils::pathsToQList(res); + + // 2.1) update srcOrigin_ and leftBoundWrapped with the point with minimum X + QDoubleVector2D lb(qInf(), qInf()); + for (const QList<QDoubleVector2D> &path: clippedPaths) + for (const QDoubleVector2D &p: path) + if (p.x() < lb.x() || (p.x() == lb.x() && p.y() < lb.y())) + // y-minimization needed to find the same point on polygon and border + lb = p; + + if (qIsInf(lb.x())) // e.g., when the polygon is clipped entirely + return; + + // 2.2) Prevent the conversion to and from clipper from introducing negative offsets which + // in turn will make the geometry wrap around. + lb.setX(qMax(wrappedLeftBound.x(), lb.x())); + leftBoundWrapped = lb; + srcOrigin_ = map.geoProjection().mapProjectionToGeo(map.geoProjection().unwrapMapProjection(lb)); + } else { + clippedPaths.append(wrappedPath); + } + + // 3) + QDoubleVector2D origin = map.geoProjection().wrappedMapProjectionToItemPosition(leftBoundWrapped); + for (const QList<QDoubleVector2D> &path: clippedPaths) { + QDoubleVector2D lastAddedPoint; + for (int i = 0; i < path.size(); ++i) { + QDoubleVector2D point = map.geoProjection().wrappedMapProjectionToItemPosition(path.at(i)); + point = point - origin; // (0,0) if point == geoLeftBound_ + + if (i == 0) { + srcPath_.moveTo(point.toPointF()); + lastAddedPoint = point; + } else { + if ((point - lastAddedPoint).manhattanLength() > 3 || + i == path.size() - 1) { + srcPath_.lineTo(point.toPointF()); + lastAddedPoint = point; + } + } + } + srcPath_.closeSubpath(); + } + + if (!assumeSimple_) + srcPath_ = srcPath_.simplified(); + + sourceBounds_ = srcPath_.boundingRect(); +} + +/*! + \internal +*/ +void QGeoMapPolygonGeometry::updateScreenPoints(const QGeoMap &map) +{ + if (!screenDirty_) + return; + + if (map.viewportWidth() == 0 || map.viewportHeight() == 0) { + clear(); + return; + } + + QDoubleVector2D origin = map.geoProjection().coordinateToItemPosition(srcOrigin_, false); + + // Create the viewport rect in the same coordinate system + // as the actual points + QRectF viewport(0, 0, map.viewportWidth(), map.viewportHeight()); + viewport.translate(-1 * origin.toPointF()); + + QPainterPath vpPath; + vpPath.addRect(viewport); + + // The geometry has already been clipped against the visible region projection in wrapped mercator space. + QPainterPath ppi = srcPath_; + clear(); + + // a polygon requires at least 3 points; + if (ppi.elementCount() < 3) + return; + + // Intersection between the viewport and a concave polygon can create multiple polygons + // joined by a line at the viewport border, and poly2tri does not triangulate this very well + // so use the full src path if the resulting polygon is concave. + if (clipToViewport_) { + int changeInX = 0; + int changeInY = 0; + QPainterPath::Element e1 = ppi.elementAt(1); + QPainterPath::Element e = ppi.elementAt(0); + QVector2D edgeA(e1.x - e.x ,e1.y - e.y); + for (int i = 2; i <= ppi.elementCount(); ++i) { + e = ppi.elementAt(i % ppi.elementCount()); + if (e.x == e1.x && e.y == e1.y) + continue; + QVector2D edgeB(e.x - e1.x, e.y - e1.y); + if ((edgeA.x() < 0) == (edgeB.x() >= 0)) + changeInX++; + if ((edgeA.y() < 0) == (edgeB.y() >= 0)) + changeInY++; + edgeA = edgeB; + e1 = e; + } + if (changeInX > 2 || changeInY > 2) // polygon is concave + ppi = srcPath_; + } + + // translate the path into top-left-centric coordinates + QRectF bb = ppi.boundingRect(); + ppi.translate(-bb.left(), -bb.top()); + firstPointOffset_ = -1 * bb.topLeft(); + + ppi.closeSubpath(); + + screenOutline_ = ppi; + + QTriangleSet ts = qTriangulate(ppi); + qreal *vx = ts.vertices.data(); + + screenIndices_.reserve(ts.indices.size()); + screenVertices_.reserve(ts.vertices.size()); + + if (ts.indices.type() == QVertexIndexVector::UnsignedInt) { + const quint32 *ix = reinterpret_cast<const quint32 *>(ts.indices.data()); + for (int i = 0; i < (ts.indices.size()/3*3); ++i) + screenIndices_ << ix[i]; + } else { + const quint16 *ix = reinterpret_cast<const quint16 *>(ts.indices.data()); + for (int i = 0; i < (ts.indices.size()/3*3); ++i) + screenIndices_ << ix[i]; + } + for (int i = 0; i < (ts.vertices.size()/2*2); i += 2) + screenVertices_ << QPointF(vx[i], vx[i + 1]); + + screenBounds_ = ppi.boundingRect(); +} + +QDeclarativePolygonMapItem::QDeclarativePolygonMapItem(QQuickItem *parent) +: QDeclarativeGeoMapItemBase(parent), border_(this), color_(Qt::transparent), dirtyMaterial_(true), + updatingGeometry_(false) +{ + setFlag(ItemHasContents, true); + QObject::connect(&border_, SIGNAL(colorChanged(QColor)), + this, SLOT(handleBorderUpdated())); + QObject::connect(&border_, SIGNAL(widthChanged(qreal)), + this, SLOT(handleBorderUpdated())); +} + +/*! + \internal +*/ +void QDeclarativePolygonMapItem::handleBorderUpdated() +{ + borderGeometry_.markSourceDirty(); + polishAndUpdate(); +} + +QDeclarativePolygonMapItem::~QDeclarativePolygonMapItem() +{ +} + +/*! + \qmlpropertygroup Location::MapPolygon::border + \qmlproperty int MapPolygon::border.width + \qmlproperty color MapPolygon::border.color + + This property is part of the border property group. The border property + group holds the width and color used to draw the border of the polygon. + + The width is in pixels and is independent of the zoom level of the map. + + The default values correspond to a black border with a width of 1 pixel. + For no line, use a width of 0 or a transparent color. +*/ + +QDeclarativeMapLineProperties *QDeclarativePolygonMapItem::border() +{ + return &border_; +} + +/*! + \internal +*/ +void QDeclarativePolygonMapItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) +{ + QDeclarativeGeoMapItemBase::setMap(quickMap,map); + if (map) { + regenerateCache(); + geometry_.markSourceDirty(); + borderGeometry_.markSourceDirty(); + polishAndUpdate(); + } +} + +/*! + \qmlproperty list<coordinate> MapPolygon::path + + This property holds the ordered list of coordinates which + define the polygon. + + \sa addCoordinate, removeCoordinate +*/ +QJSValue QDeclarativePolygonMapItem::path() const +{ + QQmlContext *context = QQmlEngine::contextForObject(this); + QQmlEngine *engine = context->engine(); + QV4::ExecutionEngine *v4 = QQmlEnginePrivate::getV4Engine(engine); + + QV4::Scope scope(v4); + QV4::Scoped<QV4::ArrayObject> pathArray(scope, v4->newArrayObject(geopath_.path().length())); + for (int i = 0; i < geopath_.path().length(); ++i) { + const QGeoCoordinate &c = geopath_.coordinateAt(i); + + QV4::ScopedValue cv(scope, v4->fromVariant(QVariant::fromValue(c))); + pathArray->putIndexed(i, cv); + } + + return QJSValue(v4, pathArray.asReturnedValue()); +} + +void QDeclarativePolygonMapItem::setPath(const QJSValue &value) +{ + if (!value.isArray()) + return; + + QList<QGeoCoordinate> pathList; + quint32 length = value.property(QStringLiteral("length")).toUInt(); + for (quint32 i = 0; i < length; ++i) { + bool ok; + QGeoCoordinate c = parseCoordinate(value.property(i), &ok); + + if (!ok || !c.isValid()) { + qmlWarning(this) << "Unsupported path type"; + return; + } + + pathList.append(c); + } + + // Equivalent to QDeclarativePolylineMapItem::setPathFromGeoList + if (geopath_.path() == pathList) + return; + + geopath_.setPath(pathList); + + regenerateCache(); + geometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); + borderGeometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); + markSourceDirtyAndUpdate(); + emit pathChanged(); +} + +/*! + \qmlmethod void MapPolygon::addCoordinate(coordinate) + + Adds a coordinate to the path. + + \sa removeCoordinate, path +*/ + +void QDeclarativePolygonMapItem::addCoordinate(const QGeoCoordinate &coordinate) +{ + if (!coordinate.isValid()) + return; + + geopath_.addCoordinate(coordinate); + updateCache(); + geometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); + borderGeometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); + markSourceDirtyAndUpdate(); + emit pathChanged(); +} + +/*! + \qmlmethod void MapPolygon::removeCoordinate(coordinate) + + Removes \a coordinate from the path. If there are multiple instances of the + same coordinate, the one added last is removed. + + If \a coordinate is not in the path this method does nothing. + + \sa addCoordinate, path +*/ +void QDeclarativePolygonMapItem::removeCoordinate(const QGeoCoordinate &coordinate) +{ + int length = geopath_.path().length(); + geopath_.removeCoordinate(coordinate); + if (geopath_.path().length() == length) + return; + + regenerateCache(); + geometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); + borderGeometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); + markSourceDirtyAndUpdate(); + emit pathChanged(); +} + +/*! + \qmlproperty color MapPolygon::color + + This property holds the color used to fill the polygon. + + The default value is transparent. +*/ + +QColor QDeclarativePolygonMapItem::color() const +{ + return color_; +} + +void QDeclarativePolygonMapItem::setColor(const QColor &color) +{ + if (color_ == color) + return; + + color_ = color; + dirtyMaterial_ = true; + update(); + emit colorChanged(color_); +} + +/*! + \internal +*/ +QSGNode *QDeclarativePolygonMapItem::updateMapItemPaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) +{ + Q_UNUSED(data); + MapPolygonNode *node = static_cast<MapPolygonNode *>(oldNode); + + if (!node) + node = new MapPolygonNode(); + + //TODO: update only material + if (geometry_.isScreenDirty() || borderGeometry_.isScreenDirty() || dirtyMaterial_) { + node->update(color_, border_.color(), &geometry_, &borderGeometry_); + geometry_.setPreserveGeometry(false); + borderGeometry_.setPreserveGeometry(false); + geometry_.markClean(); + borderGeometry_.markClean(); + dirtyMaterial_ = false; + } + return node; +} + +/*! + \internal +*/ +void QDeclarativePolygonMapItem::updatePolish() +{ + if (!map() || geopath_.path().length() == 0) + return; + + QScopedValueRollback<bool> rollback(updatingGeometry_); + updatingGeometry_ = true; + + geometry_.updateSourcePoints(*map(), geopathProjected_); + geometry_.updateScreenPoints(*map()); + + QList<QGeoMapItemGeometry *> geoms; + geoms << &geometry_; + borderGeometry_.clear(); + + if (border_.color() != Qt::transparent && border_.width() > 0) { + QList<QDoubleVector2D> closedPath = geopathProjected_; + closedPath << closedPath.first(); + + borderGeometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); + + const QGeoCoordinate &geometryOrigin = geometry_.origin(); + + borderGeometry_.srcPoints_.clear(); + borderGeometry_.srcPointTypes_.clear(); + + QDoubleVector2D borderLeftBoundWrapped; + QList<QList<QDoubleVector2D > > clippedPaths = borderGeometry_.clipPath(*map(), closedPath, borderLeftBoundWrapped); + if (clippedPaths.size()) { + borderLeftBoundWrapped = map()->geoProjection().geoToWrappedMapProjection(geometryOrigin); + borderGeometry_.pathToScreen(*map(), clippedPaths, borderLeftBoundWrapped); + borderGeometry_.updateScreenPoints(*map(), border_.width()); + + geoms << &borderGeometry_; + } else { + borderGeometry_.clear(); + } + } + + QRectF combined = QGeoMapItemGeometry::translateToCommonOrigin(geoms); + setWidth(combined.width()); + setHeight(combined.height()); + + setPositionOnMap(geometry_.origin(), -1 * geometry_.sourceBoundingBox().topLeft()); +} + +void QDeclarativePolygonMapItem::markSourceDirtyAndUpdate() +{ + geometry_.markSourceDirty(); + borderGeometry_.markSourceDirty(); + polishAndUpdate(); +} + +/*! + \internal +*/ +void QDeclarativePolygonMapItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event) +{ + if (event.mapSize.width() <= 0 || event.mapSize.height() <= 0) + return; + + geometry_.setPreserveGeometry(true, geometry_.geoLeftBound()); + borderGeometry_.setPreserveGeometry(true, borderGeometry_.geoLeftBound()); + geometry_.markSourceDirty(); + borderGeometry_.markSourceDirty(); + polishAndUpdate(); +} + +/*! + \internal +*/ +void QDeclarativePolygonMapItem::regenerateCache() +{ + if (!map()) + return; + geopathProjected_.clear(); + geopathProjected_.reserve(geopath_.path().size()); + for (const QGeoCoordinate &c : geopath_.path()) + geopathProjected_ << map()->geoProjection().geoToMapProjection(c); +} + +/*! + \internal +*/ +void QDeclarativePolygonMapItem::updateCache() +{ + if (!map()) + return; + geopathProjected_ << map()->geoProjection().geoToMapProjection(geopath_.path().last()); +} + +/*! + \internal +*/ +bool QDeclarativePolygonMapItem::contains(const QPointF &point) const +{ + return (geometry_.contains(point) || borderGeometry_.contains(point)); +} + +const QGeoShape &QDeclarativePolygonMapItem::geoShape() const +{ + return geopath_; +} + +QGeoMap::ItemType QDeclarativePolygonMapItem::itemType() const +{ + return QGeoMap::MapPolygon; +} + +/*! + \internal +*/ +void QDeclarativePolygonMapItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + if (!map() || !geopath_.isValid() || updatingGeometry_ || newGeometry.topLeft() == oldGeometry.topLeft()) { + QDeclarativeGeoMapItemBase::geometryChanged(newGeometry, oldGeometry); + return; + } + // TODO: change the algorithm to preserve the distances and size! + QGeoCoordinate newCenter = map()->geoProjection().itemPositionToCoordinate(QDoubleVector2D(newGeometry.center()), false); + QGeoCoordinate oldCenter = map()->geoProjection().itemPositionToCoordinate(QDoubleVector2D(oldGeometry.center()), false); + if (!newCenter.isValid() || !oldCenter.isValid()) + return; + double offsetLongi = newCenter.longitude() - oldCenter.longitude(); + double offsetLati = newCenter.latitude() - oldCenter.latitude(); + if (offsetLati == 0.0 && offsetLongi == 0.0) + return; + + geopath_.translate(offsetLati, offsetLongi); + regenerateCache(); + geometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); + borderGeometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); + markSourceDirtyAndUpdate(); + emit pathChanged(); + + // Not calling QDeclarativeGeoMapItemBase::geometryChanged() as it will be called from a nested + // call to this function. +} + +////////////////////////////////////////////////////////////////////// + +MapPolygonNode::MapPolygonNode() : + border_(new MapPolylineNode()), + geometry_(QSGGeometry::defaultAttributes_Point2D(), 0), + blocked_(true) +{ + geometry_.setDrawingMode(QSGGeometry::DrawTriangles); + QSGGeometryNode::setMaterial(&fill_material_); + QSGGeometryNode::setGeometry(&geometry_); + + appendChildNode(border_); +} + +MapPolygonNode::~MapPolygonNode() +{ +} + +/*! + \internal +*/ +bool MapPolygonNode::isSubtreeBlocked() const +{ + return blocked_; +} + +/*! + \internal +*/ +void MapPolygonNode::update(const QColor &fillColor, const QColor &borderColor, + const QGeoMapItemGeometry *fillShape, + const QGeoMapItemGeometry *borderShape) +{ + /* Do the border update first */ + border_->update(borderColor, borderShape); + + /* If we have neither fill nor border with valid points, block the whole + * tree. We can't just block the fill without blocking the border too, so + * we're a little conservative here (maybe at the expense of rendering + * accuracy) */ + if (fillShape->size() == 0) { + if (borderShape->size() == 0) { + blocked_ = true; + return; + } else { + blocked_ = false; + } + } else { + blocked_ = false; + } + + QSGGeometry *fill = QSGGeometryNode::geometry(); + fillShape->allocateAndFill(fill); + markDirty(DirtyGeometry); + + if (fillColor != fill_material_.color()) { + fill_material_.setColor(fillColor); + setMaterial(&fill_material_); + markDirty(DirtyMaterial); + } +} + +QT_END_NAMESPACE diff --git a/src/location/declarativemaps/qdeclarativepolygonmapitem_p.h b/src/location/declarativemaps/qdeclarativepolygonmapitem_p.h new file mode 100644 index 00000000..a928ae39 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativepolygonmapitem_p.h @@ -0,0 +1,164 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEPOLYGONMAPITEM +#define QDECLARATIVEPOLYGONMAPITEM + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtLocation/private/qlocationglobal_p.h> +#include <QtLocation/private/qdeclarativegeomapitembase_p.h> +#include <QtLocation/private/qdeclarativepolylinemapitem_p.h> +#include <QtLocation/private/qgeomapitemgeometry_p.h> + +#include <QSGGeometryNode> +#include <QSGFlatColorMaterial> + +QT_BEGIN_NAMESPACE + +class MapPolygonNode; + +class QGeoMapPolygonGeometry : public QGeoMapItemGeometry +{ +public: + QGeoMapPolygonGeometry(); + + inline void setAssumeSimple(bool value) { assumeSimple_ = value; } + + void updateSourcePoints(const QGeoMap &map, + const QList<QDoubleVector2D> &path); + + void updateScreenPoints(const QGeoMap &map); + +protected: + QPainterPath srcPath_; + bool assumeSimple_; +}; + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativePolygonMapItem : public QDeclarativeGeoMapItemBase +{ + Q_OBJECT + + Q_PROPERTY(QJSValue path READ path WRITE setPath NOTIFY pathChanged) + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) + Q_PROPERTY(QDeclarativeMapLineProperties *border READ border CONSTANT) + +public: + explicit QDeclarativePolygonMapItem(QQuickItem *parent = 0); + ~QDeclarativePolygonMapItem(); + + virtual void setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) Q_DECL_OVERRIDE; + //from QuickItem + virtual QSGNode *updateMapItemPaintNode(QSGNode *, UpdatePaintNodeData *) Q_DECL_OVERRIDE; + + Q_INVOKABLE void addCoordinate(const QGeoCoordinate &coordinate); + Q_INVOKABLE void removeCoordinate(const QGeoCoordinate &coordinate); + + QJSValue path() const; + void setPath(const QJSValue &value); + + QColor color() const; + void setColor(const QColor &color); + + QDeclarativeMapLineProperties *border(); + + bool contains(const QPointF &point) const Q_DECL_OVERRIDE; + const QGeoShape &geoShape() const Q_DECL_OVERRIDE; + QGeoMap::ItemType itemType() const Q_DECL_OVERRIDE; + +Q_SIGNALS: + void pathChanged(); + void colorChanged(const QColor &color); + +protected: + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) Q_DECL_OVERRIDE; + void updatePolish() Q_DECL_OVERRIDE; + +protected Q_SLOTS: + void markSourceDirtyAndUpdate(); + void handleBorderUpdated(); + virtual void afterViewportChanged(const QGeoMapViewportChangeEvent &event) Q_DECL_OVERRIDE; + +private: + void regenerateCache(); + void updateCache(); + + QGeoPath geopath_; + QList<QDoubleVector2D> geopathProjected_; + QDeclarativeMapLineProperties border_; + QColor color_; + bool dirtyMaterial_; + QGeoMapPolygonGeometry geometry_; + QGeoMapPolylineGeometry borderGeometry_; + bool updatingGeometry_; +}; + +////////////////////////////////////////////////////////////////////// + +class MapPolygonNode : public QSGGeometryNode +{ + +public: + MapPolygonNode(); + ~MapPolygonNode(); + + void update(const QColor &fillColor, const QColor &borderColor, + const QGeoMapItemGeometry *fillShape, + const QGeoMapItemGeometry *borderShape); + + bool isSubtreeBlocked() const; + +private: + QSGFlatColorMaterial fill_material_; + MapPolylineNode *border_; + QSGGeometry geometry_; + bool blocked_; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativePolygonMapItem) + +#endif /* QDECLARATIVEPOLYGONMAPITEM_H_ */ diff --git a/src/location/declarativemaps/qdeclarativepolylinemapitem.cpp b/src/location/declarativemaps/qdeclarativepolylinemapitem.cpp new file mode 100644 index 00000000..7c75cd7a --- /dev/null +++ b/src/location/declarativemaps/qdeclarativepolylinemapitem.cpp @@ -0,0 +1,991 @@ +/**************************************************************************** + ** + ** Copyright (C) 2015 The Qt Company Ltd. + ** Contact: http://www.qt.io/licensing/ + ** + ** This file is part of the QtLocation module of the Qt Toolkit. + ** + ** $QT_BEGIN_LICENSE:LGPL3$ + ** 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 http://www.qt.io/terms-conditions. For further + ** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free + ** Software Foundation and appearing in the file LICENSE.GPL included in + ** the packaging of this file. Please review the following information to + ** ensure the GNU General Public License version 2.0 requirements will be + ** met: http://www.gnu.org/licenses/gpl-2.0.html. + ** + ** $QT_END_LICENSE$ + ** + ****************************************************************************/ + +#include "qdeclarativepolylinemapitem_p.h" +#include "qlocationutils_p.h" +#include "error_messages.h" +#include "locationvaluetypehelper_p.h" +#include "qdoublevector2d_p.h" +#include <QtLocation/private/qgeomap_p.h> + +#include <QtCore/QScopedValueRollback> +#include <QtQml/QQmlInfo> +#include <QtQml/private/qqmlengine_p.h> +#include <QPainter> +#include <QPainterPath> +#include <QPainterPathStroker> +#include <qnumeric.h> + +#include <QtGui/private/qvectorpath_p.h> +#include <QtGui/private/qtriangulatingstroker_p.h> +#include <QtGui/private/qtriangulator_p.h> + +#include <QtPositioning/private/qclipperutils_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype MapPolyline + \instantiates QDeclarativePolylineMapItem + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-maps + \since Qt Location 5.0 + + \brief The MapPolyline type displays a polyline on a map. + + The MapPolyline type displays a polyline on a map, specified in terms of an ordered list of + \l {coordinate}{coordinates}. The \l {coordinate}{coordinates} on + the path cannot be directly changed after being added to the Polyline. Instead, copy the + \l path into a var, modify the copy and reassign the copy back to the \l path. + + \code + var path = mapPolyline.path; + path[0].latitude = 5; + mapPolyline.path = path; + \endcode + + Coordinates can also be added and removed at any time using the \l addCoordinate and + \l removeCoordinate methods. + + By default, the polyline is displayed as a 1-pixel thick black line. This + can be changed using the \l line.width and \l line.color properties. + + \section2 Performance + + MapPolylines have a rendering cost that is O(n) with respect to the number + of vertices. This means that the per frame cost of having a polyline on + the Map grows in direct proportion to the number of points in the polyline. + + Like the other map objects, MapPolyline is normally drawn without a smooth + appearance. Setting the \l {Item::opacity}{opacity} property will force the object to + be blended, which decreases performance considerably depending on the hardware in use. + + \note MapPolylines are implemented using the OpenGL GL_LINES + primitive. There have been occasional reports of issues and rendering + inconsistencies on some (particularly quite old) platforms. No workaround + is yet available for these issues. + + \section2 Example Usage + + The following snippet shows a MapPolyline with 4 points, making a shape + like the top part of a "question mark" (?), near Brisbane, Australia. + The line drawn is 3 pixels in width and green in color. + + \code + Map { + MapPolyline { + line.width: 3 + line.color: 'green' + path: [ + { latitude: -27, longitude: 153.0 }, + { latitude: -27, longitude: 154.1 }, + { latitude: -28, longitude: 153.5 }, + { latitude: -29, longitude: 153.5 } + ] + } + } + \endcode + + \image api-mappolyline.png +*/ + +QDeclarativeMapLineProperties::QDeclarativeMapLineProperties(QObject *parent) : + QObject(parent), + width_(1.0), + color_(Qt::black) +{ +} + +/*! + \internal +*/ +QColor QDeclarativeMapLineProperties::color() const +{ + return color_; +} + +/*! + \internal +*/ +void QDeclarativeMapLineProperties::setColor(const QColor &color) +{ + if (color_ == color) + return; + + color_ = color; + emit colorChanged(color_); +} + +/*! + \internal +*/ +qreal QDeclarativeMapLineProperties::width() const +{ + return width_; +} + +/*! + \internal +*/ +void QDeclarativeMapLineProperties::setWidth(qreal width) +{ + if (width_ == width) + return; + + width_ = width; + emit widthChanged(width_); +} + +struct Vertex +{ + QVector2D position; +}; + +QGeoMapPolylineGeometry::QGeoMapPolylineGeometry() +{ +} + +QList<QList<QDoubleVector2D> > QGeoMapPolylineGeometry::clipPath(const QGeoMap &map, + const QList<QDoubleVector2D> &path, + QDoubleVector2D &leftBoundWrapped) +{ + /* + * Approach: + * 1) project coordinates to wrapped web mercator, and do unwrapBelowX + * 2) if the scene is tilted, clip the geometry against the visible region (this may generate multiple polygons) + * 2.1) recalculate the origin and geoLeftBound to prevent these parameters from ending in unprojectable areas + * 2.2) ensure the left bound does not wrap around due to QGeoCoordinate <-> clipper conversions + */ + + srcOrigin_ = geoLeftBound_; + + double unwrapBelowX = 0; + leftBoundWrapped = map.geoProjection().wrapMapProjection(map.geoProjection().geoToMapProjection(geoLeftBound_)); + if (preserveGeometry_) + unwrapBelowX = leftBoundWrapped.x(); + + QList<QDoubleVector2D> wrappedPath; + wrappedPath.reserve(path.size()); + QDoubleVector2D wrappedLeftBound(qInf(), qInf()); + // 1) + for (int i = 0; i < path.size(); ++i) { + const QDoubleVector2D &coord = path.at(i); + QDoubleVector2D wrappedProjection = map.geoProjection().wrapMapProjection(coord); + + // We can get NaN if the map isn't set up correctly, or the projection + // is faulty -- probably best thing to do is abort + if (!qIsFinite(wrappedProjection.x()) || !qIsFinite(wrappedProjection.y())) + return QList<QList<QDoubleVector2D> >(); + + const bool isPointLessThanUnwrapBelowX = (wrappedProjection.x() < leftBoundWrapped.x()); + // unwrap x to preserve geometry if moved to border of map + if (preserveGeometry_ && isPointLessThanUnwrapBelowX) { + double distance = wrappedProjection.x() - unwrapBelowX; + if (distance < 0.0) + distance += 1.0; + wrappedProjection.setX(unwrapBelowX + distance); + } + if (wrappedProjection.x() < wrappedLeftBound.x() || (wrappedProjection.x() == wrappedLeftBound.x() && wrappedProjection.y() < wrappedLeftBound.y())) { + wrappedLeftBound = wrappedProjection; + } + wrappedPath.append(wrappedProjection); + } + + // 2) + QList<QList<QDoubleVector2D> > clippedPaths; + const QList<QDoubleVector2D> &visibleRegion = map.geoProjection().visibleRegion(); + if (visibleRegion.size()) { + c2t::clip2tri clipper; + clipper.addSubjectPath(QClipperUtils::qListToPath(wrappedPath), false); + clipper.addClipPolygon(QClipperUtils::qListToPath(visibleRegion)); + Paths res = clipper.execute(c2t::clip2tri::Intersection); + clippedPaths = QClipperUtils::pathsToQList(res); + + // 2.1) update srcOrigin_ and leftBoundWrapped with the point with minimum X + QDoubleVector2D lb(qInf(), qInf()); + for (const QList<QDoubleVector2D> &path: clippedPaths) { + for (const QDoubleVector2D &p: path) { + if (p == leftBoundWrapped) { + lb = p; + break; + } else if (p.x() < lb.x() || (p.x() == lb.x() && p.y() < lb.y())) { + // y-minimization needed to find the same point on polygon and border + lb = p; + } + } + } + if (qIsInf(lb.x())) + return QList<QList<QDoubleVector2D> >(); + + // 2.2) Prevent the conversion to and from clipper from introducing negative offsets which + // in turn will make the geometry wrap around. + lb.setX(qMax(wrappedLeftBound.x(), lb.x())); + leftBoundWrapped = lb; + } else { + clippedPaths.append(wrappedPath); + } + + return clippedPaths; +} + +void QGeoMapPolylineGeometry::pathToScreen(const QGeoMap &map, + const QList<QList<QDoubleVector2D> > &clippedPaths, + const QDoubleVector2D &leftBoundWrapped) +{ + // 3) project the resulting geometry to screen position and calculate screen bounds + double minX = qInf(); + double minY = qInf(); + double maxX = -qInf(); + double maxY = -qInf(); + + srcOrigin_ = map.geoProjection().mapProjectionToGeo(map.geoProjection().unwrapMapProjection(leftBoundWrapped)); + QDoubleVector2D origin = map.geoProjection().wrappedMapProjectionToItemPosition(leftBoundWrapped); + for (const QList<QDoubleVector2D> &path: clippedPaths) { + QDoubleVector2D lastAddedPoint; + for (int i = 0; i < path.size(); ++i) { + QDoubleVector2D point = map.geoProjection().wrappedMapProjectionToItemPosition(path.at(i)); + + point = point - origin; // (0,0) if point == geoLeftBound_ + + minX = qMin(point.x(), minX); + minY = qMin(point.y(), minY); + maxX = qMax(point.x(), maxX); + maxY = qMax(point.y(), maxY); + + if (i == 0) { + srcPoints_ << point.x() << point.y(); + srcPointTypes_ << QPainterPath::MoveToElement; + lastAddedPoint = point; + } else { + if ((point - lastAddedPoint).manhattanLength() > 3 || + i == path.size() - 1) { + srcPoints_ << point.x() << point.y(); + srcPointTypes_ << QPainterPath::LineToElement; + lastAddedPoint = point; + } + } + } + } + + sourceBounds_ = QRectF(QPointF(minX, minY), QPointF(maxX, maxY)); +} + +/*! + \internal +*/ +void QGeoMapPolylineGeometry::updateSourcePoints(const QGeoMap &map, + const QList<QDoubleVector2D> &path, + const QGeoCoordinate geoLeftBound) +{ + if (!sourceDirty_) + return; + + geoLeftBound_ = geoLeftBound; + + // clear the old data and reserve enough memory + srcPoints_.clear(); + srcPoints_.reserve(path.size() * 2); + srcPointTypes_.clear(); + srcPointTypes_.reserve(path.size()); + + /* + * Approach: + * 1) project coordinates to wrapped web mercator, and do unwrapBelowX + * 2) if the scene is tilted, clip the geometry against the visible region (this may generate multiple polygons) + * 3) project the resulting geometry to screen position and calculate screen bounds + */ + + QDoubleVector2D leftBoundWrapped; + // 1, 2) + const QList<QList<QDoubleVector2D> > &clippedPaths = clipPath(map, path, leftBoundWrapped); + + // 3) + pathToScreen(map, clippedPaths, leftBoundWrapped); +} + +//////////////////////////////////////////////////////////////////////////// +#if 0 // Old polyline to viewport clipping code. Retaining it for now. +/* Polyline clip */ + +enum ClipPointType { + InsidePoint = 0x00, + LeftPoint = 0x01, + RightPoint = 0x02, + BottomPoint = 0x04, + TopPoint = 0x08 +}; + +static inline int clipPointType(qreal x, qreal y, const QRectF &rect) +{ + int type = InsidePoint; + if (x < rect.left()) + type |= LeftPoint; + else if (x > rect.right()) + type |= RightPoint; + if (y < rect.top()) + type |= TopPoint; + else if (y > rect.bottom()) + type |= BottomPoint; + return type; +} + +static void clipSegmentToRect(qreal x0, qreal y0, qreal x1, qreal y1, + const QRectF &clipRect, + QVector<qreal> &outPoints, + QVector<QPainterPath::ElementType> &outTypes) +{ + int type0 = clipPointType(x0, y0, clipRect); + int type1 = clipPointType(x1, y1, clipRect); + bool accept = false; + + while (true) { + if (!(type0 | type1)) { + accept = true; + break; + } else if (type0 & type1) { + break; + } else { + qreal x = 0.0; + qreal y = 0.0; + int outsideType = type0 ? type0 : type1; + + if (outsideType & BottomPoint) { + x = x0 + (x1 - x0) * (clipRect.bottom() - y0) / (y1 - y0); + y = clipRect.bottom() - 0.1; + } else if (outsideType & TopPoint) { + x = x0 + (x1 - x0) * (clipRect.top() - y0) / (y1 - y0); + y = clipRect.top() + 0.1; + } else if (outsideType & RightPoint) { + y = y0 + (y1 - y0) * (clipRect.right() - x0) / (x1 - x0); + x = clipRect.right() - 0.1; + } else if (outsideType & LeftPoint) { + y = y0 + (y1 - y0) * (clipRect.left() - x0) / (x1 - x0); + x = clipRect.left() + 0.1; + } + + if (outsideType == type0) { + x0 = x; + y0 = y; + type0 = clipPointType(x0, y0, clipRect); + } else { + x1 = x; + y1 = y; + type1 = clipPointType(x1, y1, clipRect); + } + } + } + + if (accept) { + if (outPoints.size() >= 2) { + qreal lastX, lastY; + lastY = outPoints.at(outPoints.size() - 1); + lastX = outPoints.at(outPoints.size() - 2); + + if (!qFuzzyCompare(lastY, y0) || !qFuzzyCompare(lastX, x0)) { + outTypes << QPainterPath::MoveToElement; + outPoints << x0 << y0; + } + } else { + outTypes << QPainterPath::MoveToElement; + outPoints << x0 << y0; + } + + outTypes << QPainterPath::LineToElement; + outPoints << x1 << y1; + } +} + +static void clipPathToRect(const QVector<qreal> &points, + const QVector<QPainterPath::ElementType> &types, + const QRectF &clipRect, + QVector<qreal> &outPoints, + QVector<QPainterPath::ElementType> &outTypes) +{ + outPoints.clear(); + outPoints.reserve(points.size()); + outTypes.clear(); + outTypes.reserve(types.size()); + + qreal lastX, lastY; + for (int i = 0; i < types.size(); ++i) { + if (i > 0 && types[i] != QPainterPath::MoveToElement) { + qreal x = points[i * 2], y = points[i * 2 + 1]; + clipSegmentToRect(lastX, lastY, x, y, clipRect, outPoints, outTypes); + } + + lastX = points[i * 2]; + lastY = points[i * 2 + 1]; + } +} +#endif +/*! + \internal +*/ +void QGeoMapPolylineGeometry::updateScreenPoints(const QGeoMap &map, + qreal strokeWidth) +{ + if (!screenDirty_) + return; + + QPointF origin = map.geoProjection().coordinateToItemPosition(srcOrigin_, false).toPointF(); + + if (!qIsFinite(origin.x()) || !qIsFinite(origin.y()) || srcPointTypes_.size() < 2) { // the line might have been clipped away. + clear(); + return; + } + + // Create the viewport rect in the same coordinate system + // as the actual points + QRectF viewport(0, 0, map.viewportWidth(), map.viewportHeight()); + viewport.adjust(-strokeWidth, -strokeWidth, strokeWidth, strokeWidth); + viewport.translate(-1 * origin); + + // The geometry has already been clipped against the visible region projection in wrapped mercator space. + QVector<qreal> points = srcPoints_; + QVector<QPainterPath::ElementType> types = srcPointTypes_; + + QVectorPath vp(points.data(), types.size(), types.data()); + QTriangulatingStroker ts; + // viewport is not used in the call below. + ts.process(vp, QPen(QBrush(Qt::black), strokeWidth), viewport, QPainter::Qt4CompatiblePainting); + + clear(); + + // Nothing is on the screen + if (ts.vertexCount() == 0) + return; + + // QTriangulatingStroker#vertexCount is actually the length of the array, + // not the number of vertices + screenVertices_.reserve(ts.vertexCount()); + + QRectF bb; + + QPointF pt; + const float *vs = ts.vertices(); + for (int i = 0; i < (ts.vertexCount()/2*2); i += 2) { + pt = QPointF(vs[i], vs[i + 1]); + screenVertices_ << pt; + + if (!qIsFinite(pt.x()) || !qIsFinite(pt.y())) + break; + + if (!bb.contains(pt)) { + if (pt.x() < bb.left()) + bb.setLeft(pt.x()); + + if (pt.x() > bb.right()) + bb.setRight(pt.x()); + + if (pt.y() < bb.top()) + bb.setTop(pt.y()); + + if (pt.y() > bb.bottom()) + bb.setBottom(pt.y()); + } + } + + screenBounds_ = bb; + this->translate( -1 * sourceBounds_.topLeft()); +} + +QDeclarativePolylineMapItem::QDeclarativePolylineMapItem(QQuickItem *parent) +: QDeclarativeGeoMapItemBase(parent), line_(this), dirtyMaterial_(true), updatingGeometry_(false) +{ + setFlag(ItemHasContents, true); + QObject::connect(&line_, SIGNAL(colorChanged(QColor)), + this, SLOT(updateAfterLinePropertiesChanged())); + QObject::connect(&line_, SIGNAL(widthChanged(qreal)), + this, SLOT(updateAfterLinePropertiesChanged())); +} + +QDeclarativePolylineMapItem::~QDeclarativePolylineMapItem() +{ +} + +/*! + \internal +*/ +void QDeclarativePolylineMapItem::updateAfterLinePropertiesChanged() +{ + // mark dirty just in case we're a width change + geometry_.markSourceDirty(); + polishAndUpdate(); +} + +/*! + \internal +*/ +void QDeclarativePolylineMapItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) +{ + QDeclarativeGeoMapItemBase::setMap(quickMap,map); + if (map) { + regenerateCache(); + geometry_.markSourceDirty(); + polishAndUpdate(); + } +} + +/*! + \qmlproperty list<coordinate> MapPolyline::path + + This property holds the ordered list of coordinates which + define the polyline. +*/ + +QJSValue QDeclarativePolylineMapItem::path() const +{ + QQmlContext *context = QQmlEngine::contextForObject(this); + QQmlEngine *engine = context->engine(); + QV4::ExecutionEngine *v4 = QQmlEnginePrivate::getV4Engine(engine); + + QV4::Scope scope(v4); + QV4::Scoped<QV4::ArrayObject> pathArray(scope, v4->newArrayObject(geopath_.path().length())); + for (int i = 0; i < geopath_.path().length(); ++i) { + const QGeoCoordinate &c = geopath_.coordinateAt(i); + + QV4::ScopedValue cv(scope, v4->fromVariant(QVariant::fromValue(c))); + pathArray->putIndexed(i, cv); + } + + return QJSValue(v4, pathArray.asReturnedValue()); +} + +void QDeclarativePolylineMapItem::setPath(const QJSValue &value) +{ + if (!value.isArray()) + return; + + QList<QGeoCoordinate> pathList; + quint32 length = value.property(QStringLiteral("length")).toUInt(); + for (quint32 i = 0; i < length; ++i) { + bool ok; + QGeoCoordinate c = parseCoordinate(value.property(i), &ok); + + if (!ok || !c.isValid()) { + qmlWarning(this) << "Unsupported path type"; + return; + } + + pathList.append(c); + } + + setPathFromGeoList(pathList); +} + +/*! + \internal +*/ +void QDeclarativePolylineMapItem::setPathFromGeoList(const QList<QGeoCoordinate> &path) +{ + if (geopath_.path() == path) + return; + + geopath_.setPath(path); + + regenerateCache(); + geometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); + markSourceDirtyAndUpdate(); + emit pathChanged(); +} + +/*! + \qmlmethod int MapPolyline::pathLength() + + Returns the number of coordinates of the polyline. + + \since Qt Location 5.6 + + \sa path +*/ +int QDeclarativePolylineMapItem::pathLength() const +{ + return geopath_.path().length(); +} + +/*! + \qmlmethod void MapPolyline::addCoordinate(coordinate) + + Adds a coordinate to the end of the path. + + \sa insertCoordinate, removeCoordinate, path +*/ +void QDeclarativePolylineMapItem::addCoordinate(const QGeoCoordinate &coordinate) +{ + if (!coordinate.isValid()) + return; + + geopath_.addCoordinate(coordinate); + + updateCache(); + geometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); + markSourceDirtyAndUpdate(); + emit pathChanged(); +} + +/*! + \qmlmethod void MapPolyline::insertCoordinate(index, coordinate) + + Inserts a \a coordinate to the path at the given \a index. + + \since Qt Location 5.6 + + \sa addCoordinate, removeCoordinate, path +*/ +void QDeclarativePolylineMapItem::insertCoordinate(int index, const QGeoCoordinate &coordinate) +{ + if (index < 0 || index > geopath_.path().length()) + return; + + geopath_.insertCoordinate(index, coordinate); + + regenerateCache(); + geometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); + markSourceDirtyAndUpdate(); + emit pathChanged(); +} + +/*! + \qmlmethod void MapPolyline::replaceCoordinate(index, coordinate) + + Replaces the coordinate in the current path at the given \a index + with the new \a coordinate. + + \since Qt Location 5.6 + + \sa addCoordinate, insertCoordinate, removeCoordinate, path +*/ +void QDeclarativePolylineMapItem::replaceCoordinate(int index, const QGeoCoordinate &coordinate) +{ + if (index < 0 || index >= geopath_.path().length()) + return; + + geopath_.replaceCoordinate(index, coordinate); + + regenerateCache(); + geometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); + markSourceDirtyAndUpdate(); + emit pathChanged(); +} + +/*! + \qmlmethod coordinate MapPolyline::coordinateAt(index) + + Gets the coordinate of the polyline at the given \a index. + If the index is outside the path's bounds then an invalid + coordinate is returned. + + \since Qt Location 5.6 +*/ +QGeoCoordinate QDeclarativePolylineMapItem::coordinateAt(int index) const +{ + if (index < 0 || index >= geopath_.path().length()) + return QGeoCoordinate(); + + return geopath_.coordinateAt(index); +} + +/*! + \qmlmethod coordinate MapPolyline::containsCoordinate(coordinate) + + Returns true if the given \a coordinate is part of the path. + + \since Qt Location 5.6 +*/ +bool QDeclarativePolylineMapItem::containsCoordinate(const QGeoCoordinate &coordinate) +{ + return geopath_.containsCoordinate(coordinate); +} + +/*! + \qmlmethod void MapPolyline::removeCoordinate(coordinate) + + Removes \a coordinate from the path. If there are multiple instances of the + same coordinate, the one added last is removed. + + If \a coordinate is not in the path this method does nothing. + + \sa addCoordinate, insertCoordinate, path +*/ +void QDeclarativePolylineMapItem::removeCoordinate(const QGeoCoordinate &coordinate) +{ + int length = geopath_.path().length(); + geopath_.removeCoordinate(coordinate); + if (geopath_.path().length() == length) + return; + + regenerateCache(); + markSourceDirtyAndUpdate(); + emit pathChanged(); +} + +/*! + \qmlmethod void MapPolyline::removeCoordinate(index) + + Removes a coordinate from the path at the given \a index. + + If \a index is invalid then this method does nothing. + + \since Qt Location 5.6 + + \sa addCoordinate, insertCoordinate, path +*/ +void QDeclarativePolylineMapItem::removeCoordinate(int index) +{ + if (index < 0 || index >= geopath_.path().length()) + return; + + geopath_.removeCoordinate(index); + + regenerateCache(); + geometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); + markSourceDirtyAndUpdate(); + emit pathChanged(); +} + +/*! + \qmlpropertygroup Location::MapPolyline::line + \qmlproperty int MapPolyline::line.width + \qmlproperty color MapPolyline::line.color + + This property is part of the line property group. The line + property group holds the width and color used to draw the line. + + The width is in pixels and is independent of the zoom level of the map. + The default values correspond to a black border with a width of 1 pixel. + + For no line, use a width of 0 or a transparent color. +*/ + +QDeclarativeMapLineProperties *QDeclarativePolylineMapItem::line() +{ + return &line_; +} + +/*! + \internal +*/ +void QDeclarativePolylineMapItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + if (!map() || !geopath_.isValid() || updatingGeometry_ || newGeometry.topLeft() == oldGeometry.topLeft()) { + QDeclarativeGeoMapItemBase::geometryChanged(newGeometry, oldGeometry); + return; + } + // TODO: change the algorithm to preserve the distances and size! + QGeoCoordinate newCenter = map()->geoProjection().itemPositionToCoordinate(QDoubleVector2D(newGeometry.center()), false); + QGeoCoordinate oldCenter = map()->geoProjection().itemPositionToCoordinate(QDoubleVector2D(oldGeometry.center()), false); + if (!newCenter.isValid() || !oldCenter.isValid()) + return; + double offsetLongi = newCenter.longitude() - oldCenter.longitude(); + double offsetLati = newCenter.latitude() - oldCenter.latitude(); + if (offsetLati == 0.0 && offsetLongi == 0.0) + return; + + geopath_.translate(offsetLati, offsetLongi); + regenerateCache(); + geometry_.setPreserveGeometry(true, geopath_.boundingGeoRectangle().topLeft()); + markSourceDirtyAndUpdate(); + emit pathChanged(); + + // Not calling QDeclarativeGeoMapItemBase::geometryChanged() as it will be called from a nested + // call to this function. +} + +/*! + \internal +*/ +void QDeclarativePolylineMapItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event) +{ + if (event.mapSize.width() <= 0 || event.mapSize.height() <= 0) + return; + + geometry_.setPreserveGeometry(true, geometry_.geoLeftBound()); + markSourceDirtyAndUpdate(); +} + +/*! + \internal +*/ +void QDeclarativePolylineMapItem::regenerateCache() +{ + if (!map()) + return; + geopathProjected_.clear(); + geopathProjected_.reserve(geopath_.path().size()); + for (const QGeoCoordinate &c : geopath_.path()) + geopathProjected_ << map()->geoProjection().geoToMapProjection(c); +} + +/*! + \internal +*/ +void QDeclarativePolylineMapItem::updateCache() +{ + if (!map()) + return; + geopathProjected_ << map()->geoProjection().geoToMapProjection(geopath_.path().last()); +} + +/*! + \internal +*/ +void QDeclarativePolylineMapItem::updatePolish() +{ + if (!map() || geopath_.path().length() == 0) + return; + + QScopedValueRollback<bool> rollback(updatingGeometry_); + updatingGeometry_ = true; + + geometry_.updateSourcePoints(*map(), geopathProjected_, geopath_.boundingGeoRectangle().topLeft()); + geometry_.updateScreenPoints(*map(), line_.width()); + + setWidth(geometry_.sourceBoundingBox().width()); + setHeight(geometry_.sourceBoundingBox().height()); + + setPositionOnMap(geometry_.origin(), -1 * geometry_.sourceBoundingBox().topLeft()); +} + +void QDeclarativePolylineMapItem::markSourceDirtyAndUpdate() +{ + geometry_.markSourceDirty(); + polishAndUpdate(); +} + +/*! + \internal +*/ +QSGNode *QDeclarativePolylineMapItem::updateMapItemPaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) +{ + Q_UNUSED(data); + + MapPolylineNode *node = static_cast<MapPolylineNode *>(oldNode); + + if (!node) { + node = new MapPolylineNode(); + } + + //TODO: update only material + if (geometry_.isScreenDirty() || dirtyMaterial_ || !oldNode) { + node->update(line_.color(), &geometry_); + geometry_.setPreserveGeometry(false); + geometry_.markClean(); + dirtyMaterial_ = false; + } + return node; +} + +bool QDeclarativePolylineMapItem::contains(const QPointF &point) const +{ + QVector<QPointF> vertices = geometry_.vertices(); + QPolygonF tri; + for (int i = 0; i < vertices.size(); ++i) { + tri << vertices[i]; + if (tri.size() == 3) { + if (tri.containsPoint(point,Qt::OddEvenFill)) + return true; + tri.remove(0); + } + } + + return false; +} + +const QGeoShape &QDeclarativePolylineMapItem::geoShape() const +{ + return geopath_; +} + +QGeoMap::ItemType QDeclarativePolylineMapItem::itemType() const +{ + return QGeoMap::MapPolyline; +} + +////////////////////////////////////////////////////////////////////// + +/*! + \internal +*/ +MapPolylineNode::MapPolylineNode() : + geometry_(QSGGeometry::defaultAttributes_Point2D(),0), + blocked_(true) +{ + geometry_.setDrawingMode(QSGGeometry::DrawTriangleStrip); + QSGGeometryNode::setMaterial(&fill_material_); + QSGGeometryNode::setGeometry(&geometry_); +} + + +/*! + \internal +*/ +MapPolylineNode::~MapPolylineNode() +{ +} + +/*! + \internal +*/ +bool MapPolylineNode::isSubtreeBlocked() const +{ + return blocked_; +} + +/*! + \internal +*/ +void MapPolylineNode::update(const QColor &fillColor, + const QGeoMapItemGeometry *shape) +{ + if (shape->size() == 0) { + blocked_ = true; + return; + } else { + blocked_ = false; + } + + QSGGeometry *fill = QSGGeometryNode::geometry(); + shape->allocateAndFill(fill); + markDirty(DirtyGeometry); + + if (fillColor != fill_material_.color()) { + fill_material_.setColor(fillColor); + setMaterial(&fill_material_); + markDirty(DirtyMaterial); + } +} + +QT_END_NAMESPACE diff --git a/src/location/declarativemaps/qdeclarativepolylinemapitem_p.h b/src/location/declarativemaps/qdeclarativepolylinemapitem_p.h new file mode 100644 index 00000000..ec57c980 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativepolylinemapitem_p.h @@ -0,0 +1,200 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEPOLYLINEMAPITEM +#define QDECLARATIVEPOLYLINEMAPITEM + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtLocation/private/qdeclarativegeomapitembase_p.h> +#include <QtLocation/private/qgeomapitemgeometry_p.h> + +#include <QtPositioning/QGeoPath> +#include <QtPositioning/private/qdoublevector2d_p.h> +#include <QSGGeometryNode> +#include <QSGFlatColorMaterial> + +QT_BEGIN_NAMESPACE + +class MapPolylineNode; + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativeMapLineProperties : public QObject +{ + Q_OBJECT + + Q_PROPERTY(qreal width READ width WRITE setWidth NOTIFY widthChanged) + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) + +public: + explicit QDeclarativeMapLineProperties(QObject *parent = 0); + + QColor color() const; + void setColor(const QColor &color); + + qreal width() const; + void setWidth(qreal width); + +Q_SIGNALS: + void widthChanged(qreal width); + void colorChanged(const QColor &color); + +private: + qreal width_; + QColor color_; +}; + +class QGeoMapPolylineGeometry : public QGeoMapItemGeometry +{ +public: + QGeoMapPolylineGeometry(); + + void updateSourcePoints(const QGeoMap &map, + const QList<QDoubleVector2D> &path, + const QGeoCoordinate geoLeftBound); + + void updateScreenPoints(const QGeoMap &map, + qreal strokeWidth); + +protected: + QList<QList<QDoubleVector2D> > clipPath(const QGeoMap &map, + const QList<QDoubleVector2D> &path, + QDoubleVector2D &leftBoundWrapped); + + void pathToScreen(const QGeoMap &map, + const QList<QList<QDoubleVector2D> > &clippedPaths, + const QDoubleVector2D &leftBoundWrapped); + +private: + QVector<qreal> srcPoints_; + QVector<QPainterPath::ElementType> srcPointTypes_; + + friend class QDeclarativeCircleMapItem; + friend class QDeclarativePolygonMapItem; + friend class QDeclarativeRectangleMapItem; +}; + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativePolylineMapItem : public QDeclarativeGeoMapItemBase +{ + Q_OBJECT + + Q_PROPERTY(QJSValue path READ path WRITE setPath NOTIFY pathChanged) + Q_PROPERTY(QDeclarativeMapLineProperties *line READ line CONSTANT) + +public: + explicit QDeclarativePolylineMapItem(QQuickItem *parent = 0); + ~QDeclarativePolylineMapItem(); + + virtual void setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) Q_DECL_OVERRIDE; + //from QuickItem + virtual QSGNode *updateMapItemPaintNode(QSGNode *, UpdatePaintNodeData *) Q_DECL_OVERRIDE; + + Q_INVOKABLE int pathLength() const; + Q_INVOKABLE void addCoordinate(const QGeoCoordinate &coordinate); + Q_INVOKABLE void insertCoordinate(int index, const QGeoCoordinate &coordinate); + Q_INVOKABLE void replaceCoordinate(int index, const QGeoCoordinate &coordinate); + Q_INVOKABLE QGeoCoordinate coordinateAt(int index) const; + Q_INVOKABLE bool containsCoordinate(const QGeoCoordinate &coordinate); + Q_INVOKABLE void removeCoordinate(const QGeoCoordinate &coordinate); + Q_INVOKABLE void removeCoordinate(int index); + + QJSValue path() const; + virtual void setPath(const QJSValue &value); + + bool contains(const QPointF &point) const Q_DECL_OVERRIDE; + const QGeoShape &geoShape() const Q_DECL_OVERRIDE; + QGeoMap::ItemType itemType() const Q_DECL_OVERRIDE; + + QDeclarativeMapLineProperties *line(); + +Q_SIGNALS: + void pathChanged(); + +protected: + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) Q_DECL_OVERRIDE; + void setPathFromGeoList(const QList<QGeoCoordinate> &path); + void updatePolish() Q_DECL_OVERRIDE; + +protected Q_SLOTS: + void markSourceDirtyAndUpdate(); + void updateAfterLinePropertiesChanged(); + virtual void afterViewportChanged(const QGeoMapViewportChangeEvent &event) Q_DECL_OVERRIDE; + +private: + void regenerateCache(); + void updateCache(); + + QGeoPath geopath_; + QList<QDoubleVector2D> geopathProjected_; + QDeclarativeMapLineProperties line_; + QColor color_; + bool dirtyMaterial_; + QGeoMapPolylineGeometry geometry_; + bool updatingGeometry_; +}; + +////////////////////////////////////////////////////////////////////// + +class MapPolylineNode : public QSGGeometryNode +{ + +public: + MapPolylineNode(); + ~MapPolylineNode(); + + void update(const QColor &fillColor, const QGeoMapItemGeometry *shape); + bool isSubtreeBlocked() const; + +private: + QSGFlatColorMaterial fill_material_; + QSGGeometry geometry_; + bool blocked_; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativeMapLineProperties) +QML_DECLARE_TYPE(QDeclarativePolylineMapItem) + +#endif /* QDECLARATIVEPOLYLINEMAPITEM_H_ */ diff --git a/src/location/declarativemaps/qdeclarativerectanglemapitem.cpp b/src/location/declarativemaps/qdeclarativerectanglemapitem.cpp new file mode 100644 index 00000000..79750416 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativerectanglemapitem.cpp @@ -0,0 +1,397 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdeclarativerectanglemapitem_p.h" +#include "qdeclarativepolygonmapitem_p.h" +#include "qlocationutils_p.h" +#include <QPainterPath> +#include <qnumeric.h> +#include <QRectF> +#include <QPointF> +#include <QtLocation/private/qgeomap_p.h> +#include <QtPositioning/private/qdoublevector2d_p.h> +#include <QtCore/QScopedValueRollback> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype MapRectangle + \instantiates QDeclarativeRectangleMapItem + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-maps + \since Qt Location 5.5 + + \brief The MapRectangle type displays a rectangle on a Map. + + The MapRectangle type displays a rectangle on a Map. Rectangles are a + special case of Polygon with exactly 4 vertices and 4 "straight" edges. In + this case, "straight" means that the top-left point has the same latitude + as the top-right point (the top edge), and the bottom-left point has the + same latitude as the bottom-right point (the bottom edge). Similarly, the + points on the left side have the same longitude, and the points on the + right side have the same longitude. + + To specify the rectangle, it requires a \l topLeft and \l bottomRight point, + both given by a \l {coordinate}. + + By default, the rectangle is displayed with transparent fill and a 1-pixel + thick black border. This can be changed using the \l color, \l border.color + and \l border.width properties. + + \note Similar to the \l MapPolygon type, MapRectangles are geographic + items, thus dragging a MapRectangle causes its vertices to be recalculated + in the geographic coordinate space. Apparent stretching of the item + occurs when dragged to the a different latitude, however, its edges + remain straight. + + \section2 Performance + + MapRectangles have a rendering cost identical to a MapPolygon with 4 + vertices. + + Like the other map objects, MapRectangle is normally drawn without a smooth + appearance. Setting the \l opacity property will force the object to be + blended, which decreases performance considerably depending on the hardware + in use. + + \section2 Example Usage + + The following snippet shows a map containing a MapRectangle, spanning + from (-27, 153) to (-28, 153.5), near Brisbane, Australia. The rectangle + is filled in green, with a 2 pixel black border. + + \code + Map { + MapRectangle { + color: 'green' + border.width: 2 + topLeft { + latitude: -27 + longitude: 153 + } + bottomRight { + latitude: -28 + longitude: 153.5 + } + } + } + \endcode + + \image api-maprectangle.png +*/ + +QDeclarativeRectangleMapItem::QDeclarativeRectangleMapItem(QQuickItem *parent) +: QDeclarativeGeoMapItemBase(parent), border_(this), color_(Qt::transparent), dirtyMaterial_(true), + updatingGeometry_(false) +{ + setFlag(ItemHasContents, true); + QObject::connect(&border_, SIGNAL(colorChanged(QColor)), + this, SLOT(markSourceDirtyAndUpdate())); + QObject::connect(&border_, SIGNAL(widthChanged(qreal)), + this, SLOT(markSourceDirtyAndUpdate())); +} + +QDeclarativeRectangleMapItem::~QDeclarativeRectangleMapItem() +{ +} + +/*! + \internal +*/ +void QDeclarativeRectangleMapItem::setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) +{ + QDeclarativeGeoMapItemBase::setMap(quickMap,map); + if (!map) + return; + updatePath(); + markSourceDirtyAndUpdate(); +} + +/*! + \qmlpropertygroup Location::MapRectangle::border + \qmlproperty int MapRectangle::border.width + \qmlproperty color MapRectangle::border.color + + This property is part of the border property group. The border property group + holds the width and color used to draw the border of the rectangle. + The width is in pixels and is independent of the zoom level of the map. + + The default values correspond to a black border with a width of 1 pixel. + For no line, use a width of 0 or a transparent color. +*/ +QDeclarativeMapLineProperties *QDeclarativeRectangleMapItem::border() +{ + return &border_; +} + +/*! + \qmlproperty coordinate MapRectangle::topLeft + + This property holds the top-left coordinate of the MapRectangle which + can be used to retrieve its longitude, latitude and altitude. +*/ +void QDeclarativeRectangleMapItem::setTopLeft(const QGeoCoordinate &topLeft) +{ + if (rectangle_.topLeft() == topLeft) + return; + + rectangle_.setTopLeft(topLeft); + updatePath(); + markSourceDirtyAndUpdate(); + emit topLeftChanged(topLeft); +} + +QGeoCoordinate QDeclarativeRectangleMapItem::topLeft() +{ + return rectangle_.topLeft(); +} + +/*! + \internal +*/ +void QDeclarativeRectangleMapItem::markSourceDirtyAndUpdate() +{ + geometry_.markSourceDirty(); + borderGeometry_.markSourceDirty(); + polishAndUpdate(); +} + +/*! + \qmlproperty coordinate MapRectangle::bottomRight + + This property holds the bottom-right coordinate of the MapRectangle which + can be used to retrieve its longitude, latitude and altitude. +*/ +void QDeclarativeRectangleMapItem::setBottomRight(const QGeoCoordinate &bottomRight) +{ + if (rectangle_.bottomRight() == bottomRight) + return; + + rectangle_.setBottomRight(bottomRight); + updatePath(); + markSourceDirtyAndUpdate(); + emit bottomRightChanged(bottomRight); +} + +QGeoCoordinate QDeclarativeRectangleMapItem::bottomRight() +{ + return rectangle_.bottomRight(); +} + +/*! + \qmlproperty color MapRectangle::color + + This property holds the fill color of the rectangle. For no fill, use + a transparent color. +*/ +QColor QDeclarativeRectangleMapItem::color() const +{ + return color_; +} + +void QDeclarativeRectangleMapItem::setColor(const QColor &color) +{ + if (color_ == color) + return; + color_ = color; + dirtyMaterial_ = true; + polishAndUpdate(); + emit colorChanged(color_); +} + +/*! + \qmlproperty real MapRectangle::opacity + + This property holds the opacity of the item. Opacity is specified as a + number between 0 (fully transparent) and 1 (fully opaque). The default is 1. + + An item with 0 opacity will still receive mouse events. To stop mouse events, set the + visible property of the item to false. +*/ + +/*! + \internal +*/ +QSGNode *QDeclarativeRectangleMapItem::updateMapItemPaintNode(QSGNode *oldNode, UpdatePaintNodeData *data) +{ + Q_UNUSED(data); + + MapPolygonNode *node = static_cast<MapPolygonNode *>(oldNode); + + if (!node) { + node = new MapPolygonNode(); + } + + //TODO: update only material + if (geometry_.isScreenDirty() || borderGeometry_.isScreenDirty() || dirtyMaterial_) { + node->update(color_, border_.color(), &geometry_, &borderGeometry_); + geometry_.setPreserveGeometry(false); + borderGeometry_.setPreserveGeometry(false); + geometry_.markClean(); + borderGeometry_.markClean(); + dirtyMaterial_ = false; + } + return node; +} + +/*! + \internal +*/ +void QDeclarativeRectangleMapItem::updatePolish() +{ + if (!map() || !topLeft().isValid() || !bottomRight().isValid()) + return; + + QScopedValueRollback<bool> rollback(updatingGeometry_); + updatingGeometry_ = true; + + geometry_.setPreserveGeometry(true, rectangle_.topLeft()); + geometry_.updateSourcePoints(*map(), pathMercator_); + geometry_.updateScreenPoints(*map()); + + QList<QGeoMapItemGeometry *> geoms; + geoms << &geometry_; + borderGeometry_.clear(); + + if (border_.color() != Qt::transparent && border_.width() > 0) { + QList<QDoubleVector2D> closedPath = pathMercator_; + closedPath << closedPath.first(); + + borderGeometry_.setPreserveGeometry(true, rectangle_.topLeft()); + const QGeoCoordinate &geometryOrigin = geometry_.origin(); + + borderGeometry_.srcPoints_.clear(); + borderGeometry_.srcPointTypes_.clear(); + + QDoubleVector2D borderLeftBoundWrapped; + QList<QList<QDoubleVector2D > > clippedPaths = borderGeometry_.clipPath(*map(), closedPath, borderLeftBoundWrapped); + if (clippedPaths.size()) { + borderLeftBoundWrapped = map()->geoProjection().geoToWrappedMapProjection(geometryOrigin); + borderGeometry_.pathToScreen(*map(), clippedPaths, borderLeftBoundWrapped); + borderGeometry_.updateScreenPoints(*map(), border_.width()); + + geoms << &borderGeometry_; + } else { + borderGeometry_.clear(); + } + } + + QRectF combined = QGeoMapItemGeometry::translateToCommonOrigin(geoms); + setWidth(combined.width()); + setHeight(combined.height()); + + setPositionOnMap(geometry_.origin(), geometry_.firstPointOffset()); +} + +/*! + \internal +*/ +void QDeclarativeRectangleMapItem::afterViewportChanged(const QGeoMapViewportChangeEvent &event) +{ + if (event.mapSize.width() <= 0 || event.mapSize.height() <= 0) + return; + + geometry_.setPreserveGeometry(true, rectangle_.topLeft()); + borderGeometry_.setPreserveGeometry(true, rectangle_.topLeft()); + markSourceDirtyAndUpdate(); +} + +/*! + \internal +*/ +bool QDeclarativeRectangleMapItem::contains(const QPointF &point) const +{ + return (geometry_.contains(point) || borderGeometry_.contains(point)); +} + +const QGeoShape &QDeclarativeRectangleMapItem::geoShape() const +{ + return rectangle_; +} + +QGeoMap::ItemType QDeclarativeRectangleMapItem::itemType() const +{ + return QGeoMap::MapRectangle; +} + +/*! + \internal +*/ +void QDeclarativeRectangleMapItem::updatePath() +{ + if (!map()) + return; + pathMercator_.clear(); + pathMercator_ << map()->geoProjection().geoToMapProjection(rectangle_.topLeft()); + pathMercator_ << map()->geoProjection().geoToMapProjection( + QGeoCoordinate(rectangle_.topLeft().latitude(), rectangle_.bottomRight().longitude())); + pathMercator_ << map()->geoProjection().geoToMapProjection(rectangle_.bottomRight()); + pathMercator_ << map()->geoProjection().geoToMapProjection( + QGeoCoordinate(rectangle_.bottomRight().latitude(), rectangle_.topLeft().longitude())); +} + +/*! + \internal +*/ +void QDeclarativeRectangleMapItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + if (!map() || !rectangle_.isValid() || updatingGeometry_ || newGeometry.topLeft() == oldGeometry.topLeft()) { + QDeclarativeGeoMapItemBase::geometryChanged(newGeometry, oldGeometry); + return; + } + // TODO: change the algorithm to preserve the distances and size + QGeoCoordinate newCenter = map()->geoProjection().itemPositionToCoordinate(QDoubleVector2D(newGeometry.center()), false); + QGeoCoordinate oldCenter = map()->geoProjection().itemPositionToCoordinate(QDoubleVector2D(oldGeometry.center()), false); + if (!newCenter.isValid() || !oldCenter.isValid()) + return; + double offsetLongi = newCenter.longitude() - oldCenter.longitude(); + double offsetLati = newCenter.latitude() - oldCenter.latitude(); + if (offsetLati == 0.0 && offsetLongi == 0.0) + return; + + rectangle_.translate(offsetLati, offsetLongi); + updatePath(); + geometry_.setPreserveGeometry(true, rectangle_.topLeft()); + borderGeometry_.setPreserveGeometry(true, rectangle_.topLeft()); + markSourceDirtyAndUpdate(); + emit topLeftChanged(rectangle_.topLeft()); + emit bottomRightChanged(rectangle_.bottomRight()); + + // Not calling QDeclarativeGeoMapItemBase::geometryChanged() as it will be called from a nested + // call to this function. +} + +QT_END_NAMESPACE diff --git a/src/location/declarativemaps/qdeclarativerectanglemapitem_p.h b/src/location/declarativemaps/qdeclarativerectanglemapitem_p.h new file mode 100644 index 00000000..ca7ca9b7 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativerectanglemapitem_p.h @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVERECTANGLEMAPITEM_H_ +#define QDECLARATIVERECTANGLEMAPITEM_H_ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtLocation/private/qlocationglobal_p.h> +#include <QtLocation/private/qdeclarativegeomapitembase_p.h> +#include <QtLocation/private/qgeomapitemgeometry_p.h> +#include <QtLocation/private/qdeclarativepolylinemapitem_p.h> +#include <QtLocation/private/qdeclarativepolygonmapitem_p.h> +#include <QtPositioning/private/qdoublevector2d_p.h> + +#include <QSGGeometryNode> +#include <QSGFlatColorMaterial> + +QT_BEGIN_NAMESPACE + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativeRectangleMapItem: public QDeclarativeGeoMapItemBase +{ + Q_OBJECT + + Q_PROPERTY(QGeoCoordinate topLeft READ topLeft WRITE setTopLeft NOTIFY topLeftChanged) + Q_PROPERTY(QGeoCoordinate bottomRight READ bottomRight WRITE setBottomRight NOTIFY bottomRightChanged) + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) + Q_PROPERTY(QDeclarativeMapLineProperties *border READ border) + +public: + explicit QDeclarativeRectangleMapItem(QQuickItem *parent = 0); + ~QDeclarativeRectangleMapItem(); + + virtual void setMap(QDeclarativeGeoMap *quickMap, QGeoMap *map) Q_DECL_OVERRIDE; + //from QuickItem + virtual QSGNode *updateMapItemPaintNode(QSGNode *, UpdatePaintNodeData *) Q_DECL_OVERRIDE; + + QGeoCoordinate topLeft(); + void setTopLeft(const QGeoCoordinate ¢er); + + QGeoCoordinate bottomRight(); + void setBottomRight(const QGeoCoordinate ¢er); + + QColor color() const; + void setColor(const QColor &color); + + QDeclarativeMapLineProperties *border(); + + bool contains(const QPointF &point) const Q_DECL_OVERRIDE; + const QGeoShape &geoShape() const Q_DECL_OVERRIDE; + QGeoMap::ItemType itemType() const Q_DECL_OVERRIDE; + +Q_SIGNALS: + void topLeftChanged(const QGeoCoordinate &topLeft); + void bottomRightChanged(const QGeoCoordinate &bottomRight); + void colorChanged(const QColor &color); + +protected: + void updatePath(); + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) Q_DECL_OVERRIDE; + void updatePolish() Q_DECL_OVERRIDE; + +protected Q_SLOTS: + void markSourceDirtyAndUpdate(); + virtual void afterViewportChanged(const QGeoMapViewportChangeEvent &event) Q_DECL_OVERRIDE; + +private: + QGeoRectangle rectangle_; + QDeclarativeMapLineProperties border_; + QColor color_; + bool dirtyMaterial_; + QGeoMapPolygonGeometry geometry_; + QGeoMapPolylineGeometry borderGeometry_; + bool updatingGeometry_; + QList<QDoubleVector2D> pathMercator_; +}; + +////////////////////////////////////////////////////////////////////// + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativeRectangleMapItem) + +#endif /* QDECLARATIVERECTANGLEMAPITEM_H_ */ diff --git a/src/location/declarativemaps/qdeclarativeroutemapitem.cpp b/src/location/declarativemaps/qdeclarativeroutemapitem.cpp new file mode 100644 index 00000000..7e88b9cf --- /dev/null +++ b/src/location/declarativemaps/qdeclarativeroutemapitem.cpp @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "qdeclarativeroutemapitem_p.h" +#include "qdeclarativepolylinemapitem_p.h" +#include "qdeclarativegeoroute_p.h" + +#include <QtQml/QQmlInfo> +#include <QtGui/QPainter> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype MapRoute + \instantiates QDeclarativeRouteMapItem + \inqmlmodule QtLocation + \ingroup qml-QtLocation5-maps + \since Qt Location 5.0 + + \brief The MapRoute type displays a Route on a Map. + + The MapRoute type displays a Route obtained through a RouteModel or + other means, on the Map as a Polyline following the path of the Route. + + MapRoute is really a \l MapPolyline, but with the path specified using the + \l route property instead of directly in \l {coordinate}{coordinates}. + + By default, the route is displayed as a 1-pixel thick black line. This can + be changed using the \l line.width and \l line.color properties. + + \section2 Performance + + For notes about the performance on MapRoute, refer to the documentation for + \l MapPolyline. + + \section2 Example Usage + + Here is how to draw a \l{Route}{route} on a \l{Map}{map}: + + \snippet declarative/maps.qml QtQuick import + \snippet declarative/maps.qml QtLocation import + \codeline + \snippet declarative/maps.qml MapRoute +*/ + +/*! + \qmlpropertygroup Location::MapRoute::line + \qmlproperty int MapRoute::line.width + \qmlproperty color MapRoute::line.color + + This property is part of the line property group. The line + property group holds the width and color used to draw the line. + + The width is in pixels and is independent of the zoom level of the map. + The default values correspond to a black border with a width of 1 pixel. + + For no line, use a width of 0 or a transparent color. +*/ + + +QDeclarativeRouteMapItem::QDeclarativeRouteMapItem(QQuickItem *parent) +: QDeclarativePolylineMapItem(parent), route_(0) +{ + setFlag(ItemHasContents, true); +} + +QDeclarativeRouteMapItem::~QDeclarativeRouteMapItem() +{ +} + +/*! + \qmlproperty Route MapRoute::route + + This property holds the route to be drawn which can be used + to represent one geographical route. +*/ +QDeclarativeGeoRoute *QDeclarativeRouteMapItem::route() const +{ + return route_; +} + +void QDeclarativeRouteMapItem::setRoute(QDeclarativeGeoRoute *route) +{ + if (route_ == route) + return; + + route_ = route; + + connect(route_, SIGNAL(pathChanged()), this, SLOT(updateRoutePath())); + + if (route_) + setPathFromGeoList(route_->routePath()); + + emit routeChanged(route_); +} + +void QDeclarativeRouteMapItem::updateRoutePath() +{ + setPathFromGeoList(route_->routePath()); +} + +/*! + \internal void QDeclarativeRouteMapItem::setPath(const QJSValue &value) + + Used to disable path property on the RouteMapItem + */ +void QDeclarativeRouteMapItem::setPath(const QJSValue &value) +{ + Q_UNUSED(value); + qWarning() << "Can not set the path on QDeclarativeRouteMapItem." + << "Please use the route property instead."; +} + +QT_END_NAMESPACE diff --git a/src/location/declarativemaps/qdeclarativeroutemapitem_p.h b/src/location/declarativemaps/qdeclarativeroutemapitem_p.h new file mode 100644 index 00000000..ad959837 --- /dev/null +++ b/src/location/declarativemaps/qdeclarativeroutemapitem_p.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDECLARATIVEROUTEMAPITEM_H_ +#define QDECLARATIVEROUTEMAPITEM_H_ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtLocation/private/qlocationglobal_p.h> +#include <QtLocation/private/qdeclarativegeomapitembase_p.h> +#include <QtLocation/private/qdeclarativegeomap_p.h> +#include <QtLocation/private/qdeclarativepolylinemapitem_p.h> +#include <QPen> +#include <QBrush> + +QT_BEGIN_NAMESPACE + +class QDeclarativeGeoRoute; + +class Q_LOCATION_PRIVATE_EXPORT QDeclarativeRouteMapItem : public QDeclarativePolylineMapItem +{ + Q_OBJECT + + Q_PROPERTY(QDeclarativeGeoRoute *route READ route WRITE setRoute NOTIFY routeChanged) + +public: + explicit QDeclarativeRouteMapItem(QQuickItem *parent = 0); + ~QDeclarativeRouteMapItem(); + + QDeclarativeGeoRoute *route() const; + void setRoute(QDeclarativeGeoRoute *route); + +Q_SIGNALS: + void routeChanged(const QDeclarativeGeoRoute *route); + +private slots: + void updateRoutePath(); + +protected: + void setPath(const QJSValue &value) Q_DECL_OVERRIDE; + +private: + QDeclarativeGeoRoute *route_; +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QDeclarativeRouteMapItem) + +#endif /* QDECLARATIVEROUTEMAPITEM_H_ */ diff --git a/src/location/declarativemaps/qgeomapitemgeometry.cpp b/src/location/declarativemaps/qgeomapitemgeometry.cpp new file mode 100644 index 00000000..2883c2bb --- /dev/null +++ b/src/location/declarativemaps/qgeomapitemgeometry.cpp @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeomapitemgeometry_p.h" +#include "qdeclarativegeomap_p.h" +#include "qlocationutils_p.h" +#include <QtQuick/QSGGeometry> +#include "qdoublevector2d_p.h" +#include <QtLocation/private/qgeomap_p.h> + +QT_BEGIN_NAMESPACE + +QGeoMapItemGeometry::QGeoMapItemGeometry() +: sourceDirty_(true), screenDirty_(true), clipToViewport_(true), preserveGeometry_(false) +{ +} + +/*! + \internal +*/ +void QGeoMapItemGeometry::translate(const QPointF &offset) +{ + for (int i = 0; i < screenVertices_.size(); ++i) + screenVertices_[i] += offset; + + firstPointOffset_ += offset; + screenOutline_.translate(offset); + screenBounds_.translate(offset); +} + +/*! + \internal +*/ +void QGeoMapItemGeometry::allocateAndFill(QSGGeometry *geom) const +{ + const QVector<QPointF> &vx = screenVertices_; + const QVector<quint32> &ix = screenIndices_; + + if (isIndexed()) { + geom->allocate(vx.size(), ix.size()); + if (geom->indexType() == QSGGeometry::UnsignedShortType) { + quint16 *its = geom->indexDataAsUShort(); + for (int i = 0; i < ix.size(); ++i) + its[i] = ix[i]; + } else if (geom->indexType() == QSGGeometry::UnsignedIntType) { + quint32 *its = geom->indexDataAsUInt(); + for (int i = 0; i < ix.size(); ++i) + its[i] = ix[i]; + } + } else { + geom->allocate(vx.size()); + } + + QSGGeometry::Point2D *pts = geom->vertexDataAsPoint2D(); + for (int i = 0; i < vx.size(); ++i) + pts[i].set(vx[i].x(), vx[i].y()); +} + +/*! + \internal +*/ +QRectF QGeoMapItemGeometry::translateToCommonOrigin(const QList<QGeoMapItemGeometry *> &geoms) +{ + QGeoCoordinate origin = geoms.at(0)->origin(); + + QPainterPath brects; + + // first get max offset + QPointF maxOffset = geoms.at(0)->firstPointOffset(); + foreach (QGeoMapItemGeometry *g, geoms) { + QPointF o = g->firstPointOffset(); + maxOffset.setX(qMax(o.x(), maxOffset.x())); + maxOffset.setY(qMax(o.y(), maxOffset.y())); + } + + // then translate everything + foreach (QGeoMapItemGeometry *g, geoms) { + g->translate(maxOffset - g->firstPointOffset()); + brects.addRect(g->sourceBoundingBox()); + } + + return brects.boundingRect(); +} + +/*! + \internal +*/ +double QGeoMapItemGeometry::geoDistanceToScreenWidth(const QGeoMap &map, + const QGeoCoordinate &fromCoord, + const QGeoCoordinate &toCoord) +{ + // Do not wrap around half the globe + Q_ASSERT(!qFuzzyCompare(fromCoord.longitude(), toCoord.longitude())); + + QGeoCoordinate mapMid = map.geoProjection().itemPositionToCoordinate(QDoubleVector2D(map.viewportWidth()/2.0, 0)); + double halfGeoDist = toCoord.longitude() - fromCoord.longitude(); + if (toCoord.longitude() < fromCoord.longitude()) + halfGeoDist += 360; + halfGeoDist /= 2.0; + QGeoCoordinate geoDelta = QGeoCoordinate(0, + QLocationUtils::wrapLong(mapMid.longitude() + halfGeoDist)); + QDoubleVector2D halfScreenDist = map.geoProjection().coordinateToItemPosition(geoDelta, false) + - QDoubleVector2D(map.viewportWidth()/2.0, 0); + return halfScreenDist.x() * 2.0; +} + +QT_END_NAMESPACE diff --git a/src/location/declarativemaps/qgeomapitemgeometry_p.h b/src/location/declarativemaps/qgeomapitemgeometry_p.h new file mode 100644 index 00000000..595107ae --- /dev/null +++ b/src/location/declarativemaps/qgeomapitemgeometry_p.h @@ -0,0 +1,150 @@ +/**************************************************************************** + ** + ** Copyright (C) 2015 The Qt Company Ltd. + ** Contact: http://www.qt.io/licensing/ + ** + ** This file is part of the QtLocation module of the Qt Toolkit. + ** + ** $QT_BEGIN_LICENSE:LGPL3$ + ** 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 http://www.qt.io/terms-conditions. For further + ** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free + ** Software Foundation and appearing in the file LICENSE.GPL included in + ** the packaging of this file. Please review the following information to + ** ensure the GNU General Public License version 2.0 requirements will be + ** met: http://www.gnu.org/licenses/gpl-2.0.html. + ** + ** $QT_END_LICENSE$ + ** + ****************************************************************************/ + +#ifndef QGEOMAPITEMGEOMETRY_H +#define QGEOMAPITEMGEOMETRY_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtLocation/private/qlocationglobal_p.h> + +#include <QPainterPath> +#include <QPointF> +#include <QRectF> +#include <QVector> +#include <QGeoCoordinate> +#include <QVector2D> +#include <QList> + +QT_BEGIN_NAMESPACE + +class QSGGeometry; +class QGeoMap; + +class QGeoMapItemGeometry +{ +public: + QGeoMapItemGeometry(); + + inline bool isSourceDirty() const { return sourceDirty_; } + inline bool isScreenDirty() const { return screenDirty_; } + inline void markSourceDirty() { sourceDirty_ = true; screenDirty_ = true; } + inline void markScreenDirty() { screenDirty_ = true; clipToViewport_ = true; } + inline void markFullScreenDirty() { screenDirty_ = true; clipToViewport_ = false;} + inline void markClean() { screenDirty_ = (sourceDirty_ = false); clipToViewport_ = true;} + + inline void setPreserveGeometry(bool value, const QGeoCoordinate &geoLeftBound = QGeoCoordinate()) + { + preserveGeometry_ = value; + if (preserveGeometry_) + geoLeftBound_ = geoLeftBound; + } + inline QGeoCoordinate geoLeftBound() { return geoLeftBound_; } + + inline QRectF sourceBoundingBox() const { return sourceBounds_; } + inline QRectF screenBoundingBox() const { return screenBounds_; } + + inline QPointF firstPointOffset() const { return firstPointOffset_; } + void translate(const QPointF &offset); + + inline const QGeoCoordinate &origin() const { return srcOrigin_; } + + inline bool contains(const QPointF &screenPoint) const { + return screenOutline_.contains(screenPoint); + } + + inline QVector2D vertex(quint32 index) const { + return QVector2D(screenVertices_[index]); + } + + inline QVector<QPointF> vertices() const { return screenVertices_; } + inline QVector<quint32> indices() const { return screenIndices_; } + + inline bool isIndexed() const { return (!screenIndices_.isEmpty()); } + + /* Size is # of triangles */ + inline quint32 size() const + { + if (isIndexed()) + return screenIndices_.size() / 3; + else + return screenVertices_.size() / 3; + } + + inline void clear() { firstPointOffset_ = QPointF(0,0); + screenVertices_.clear(); screenIndices_.clear(); } + + void allocateAndFill(QSGGeometry *geom) const; + + double geoDistanceToScreenWidth(const QGeoMap &map, + const QGeoCoordinate &fromCoord, + const QGeoCoordinate &toCoord); + + static QRectF translateToCommonOrigin(const QList<QGeoMapItemGeometry *> &geoms); + + +protected: + bool sourceDirty_; + bool screenDirty_; + bool clipToViewport_; + bool preserveGeometry_; + QGeoCoordinate geoLeftBound_; + + QPointF firstPointOffset_; + + QPainterPath screenOutline_; + + QRectF sourceBounds_; + QRectF screenBounds_; + + QGeoCoordinate srcOrigin_; + + QVector<QPointF> screenVertices_; + QVector<quint32> screenIndices_; +}; + +QT_END_NAMESPACE + +#endif // QGEOMAPITEMGEOMETRY_H diff --git a/src/location/declarativemaps/qquickgeomapgesturearea.cpp b/src/location/declarativemaps/qquickgeomapgesturearea.cpp new file mode 100644 index 00000000..992fa21e --- /dev/null +++ b/src/location/declarativemaps/qquickgeomapgesturearea.cpp @@ -0,0 +1,1819 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickgeomapgesturearea_p.h" +#include "qquickgeocoordinateanimation_p.h" +#include "qdeclarativegeomap_p.h" +#include "error_messages.h" + +#include <QtGui/QGuiApplication> +#include <QtGui/qevent.h> +#include <QtGui/QWheelEvent> +#include <QtGui/QStyleHints> +#include <QtQml/qqmlinfo.h> +#include <QtQuick/QQuickWindow> +#include <QPropertyAnimation> +#include <QDebug> +#include "math.h" +#include <cmath> +#include "qgeomap_p.h" +#include "qdoublevector2d_p.h" +#include "qlocationutils_p.h" +#include <QtGui/QMatrix4x4> + + +#define QML_MAP_FLICK_DEFAULTMAXVELOCITY 2500 +#define QML_MAP_FLICK_MINIMUMDECELERATION 500 +#define QML_MAP_FLICK_DEFAULTDECELERATION 2500 +#define QML_MAP_FLICK_MAXIMUMDECELERATION 10000 + +#define QML_MAP_FLICK_VELOCITY_SAMPLE_PERIOD 38 +// FlickThreshold determines how far the "mouse" must have moved +// before we perform a flick. +static const int FlickThreshold = 20; +// Really slow flicks can be annoying. +static const qreal MinimumFlickVelocity = 75.0; +// Tolerance for detecting two finger sliding start +static const qreal MaximumParallelPosition = 40.0; // in degrees +// Tolerance for detecting parallel sliding +static const qreal MaximumParallelSlidingAngle = 4.0; // in degrees +// Tolerance for starting rotation +static const qreal MinimumRotationStartingAngle = 15.0; // in degrees +// Tolerance for starting pinch +static const qreal MinimumPinchDelta = 40; // in pixels +// Tolerance for starting tilt when sliding vertical +static const qreal MinimumPanToTiltDelta = 80; // in pixels; + +static qreal distanceBetweenTouchPoints(const QPointF &p1, const QPointF &p2) +{ + return QLineF(p1, p2).length(); +} + +// Returns the new map center after anchoring coordinate to anchorPoint on the screen +// Approach: find the displacement in (wrapped) mercator space, and apply that to the center +static QGeoCoordinate anchorCoordinateToPoint(QGeoMap &map, const QGeoCoordinate &coordinate, const QPointF &anchorPoint) +{ + QDoubleVector2D centerProj = map.geoProjection().geoToWrappedMapProjection(map.cameraData().center()); + QDoubleVector2D coordProj = map.geoProjection().geoToWrappedMapProjection(coordinate); + + QDoubleVector2D anchorProj = map.geoProjection().itemPositionToWrappedMapProjection(QDoubleVector2D(anchorPoint)); + // Y-clamping done in mercatorToCoord + return map.geoProjection().wrappedMapProjectionToGeo(centerProj + coordProj - anchorProj); +} + +static qreal angleFromPoints(const QPointF &p1, const QPointF &p2) +{ + return QLineF(p1, p2).angle(); +} + +// Keeps it in +- 180 +static qreal touchAngle(const QPointF &p1, const QPointF &p2) +{ + qreal angle = angleFromPoints(p1, p2); + if (angle > 180) + angle -= 360; + return angle; +} + +// Deals with angles crossing the +-180 edge, assumes that the delta can't be > 180 +static qreal angleDelta(const qreal angle1, const qreal angle2) +{ + qreal delta = angle1 - angle2; + if (delta > 180.0) // detect crossing angle1 positive, angle2 negative, rotation counterclockwise, difference negative + delta = angle1 - angle2 - 360.0; + else if (delta < -180.0) // detect crossing angle1 negative, angle2 positive, rotation clockwise, difference positive + delta = angle1 - angle2 + 360.0; + + return delta; +} + +static bool pointDragged(const QPointF &pOld, const QPointF &pNew) +{ + static const int startDragDistance = qApp->styleHints()->startDragDistance(); + return ( qAbs(pNew.x() - pOld.x()) > startDragDistance + || qAbs(pNew.y() - pOld.y()) > startDragDistance); +} + +static qreal vectorSize(const QPointF &vector) +{ + return std::sqrt(vector.x() * vector.x() + vector.y() * vector.y()); +} + +// This linearizes the angles around 0, and keep it linear around 180, allowing to differentiate +// touch angles that are supposed to be parallel (0 or 180 depending on what finger goes first) +static qreal touchAngleTilting(const QPointF &p1, const QPointF &p2) +{ + qreal angle = angleFromPoints(p1, p2); + if (angle > 270) + angle -= 360; + return angle; +} + +static bool movingParallelVertical(const QPointF &p1old, const QPointF &p1new, const QPointF &p2old, const QPointF &p2new) +{ + if (!pointDragged(p1old, p1new) || !pointDragged(p2old, p2new)) + return false; + + QPointF v1 = p1new - p1old; + QPointF v2 = p2new - p2old; + qreal v1v2size = vectorSize(v1 + v2); + + if (v1v2size < vectorSize(v1) || v1v2size < vectorSize(v2)) // going in opposite directions + return false; + + const qreal newAngle = touchAngleTilting(p1new, p2new); + const qreal oldAngle = touchAngleTilting(p1old, p2old); + const qreal angleDiff = angleDelta(newAngle, oldAngle); + + if (qAbs(angleDiff) > MaximumParallelSlidingAngle) + return false; + + return true; +} + +QT_BEGIN_NAMESPACE + + +/*! + \qmltype MapPinchEvent + \instantiates QGeoMapPinchEvent + \inqmlmodule QtLocation + + \brief MapPinchEvent type provides basic information about pinch event. + + MapPinchEvent type provides basic information about pinch event. They are + present in handlers of MapPinch (for example pinchStarted/pinchUpdated). Events are only + guaranteed to be valid for the duration of the handler. + + Except for the \l accepted property, all properties are read-only. + + \section2 Example Usage + + The following example enables the pinch gesture on a map and reacts to the + finished event. + + \code + Map { + id: map + gesture.enabled: true + gesture.onPinchFinished:{ + var coordinate1 = map.toCoordinate(gesture.point1) + var coordinate2 = map.toCoordinate(gesture.point2) + console.log("Pinch started at:") + console.log(" Points (" + gesture.point1.x + ", " + gesture.point1.y + ") - (" + gesture.point2.x + ", " + gesture.point2.y + ")") + console.log(" Coordinates (" + coordinate1.latitude + ", " + coordinate1.longitude + ") - (" + coordinate2.latitude + ", " + coordinate2.longitude + ")") + } + } + \endcode + + \ingroup qml-QtLocation5-maps + \since Qt Location 5.0 +*/ + +/*! + \qmlproperty QPoint QtLocation::MapPinchEvent::center + + This read-only property holds the current center point. +*/ + +/*! + \qmlproperty real QtLocation::MapPinchEvent::angle + + This read-only property holds the current angle between the two points in + the range -180 to 180. Positive values for the angles mean counter-clockwise + while negative values mean the clockwise direction. Zero degrees is at the + 3 o'clock position. +*/ + +/*! + \qmlproperty QPoint QtLocation::MapPinchEvent::point1 + \qmlproperty QPoint QtLocation::MapPinchEvent::point2 + + These read-only properties hold the actual touch points generating the pinch. + The points are not in any particular order. +*/ + +/*! + \qmlproperty int QtLocation::MapPinchEvent::pointCount + + This read-only property holds the number of points currently touched. + The MapPinch will not react until two touch points have initiated a gesture, + but will remain active until all touch points have been released. +*/ + +/*! + \qmlproperty bool QtLocation::MapPinchEvent::accepted + + Setting this property to false in the \c MapPinch::onPinchStarted handler + will result in no further pinch events being generated, and the gesture + ignored. +*/ + +/*! + \qmltype MapGestureArea + \instantiates QQuickGeoMapGestureArea + + \inqmlmodule QtLocation + + \brief The MapGestureArea type provides Map gesture interaction. + + MapGestureArea objects are used as part of a Map, to provide for panning, + flicking and pinch-to-zoom gesture used on touch displays, as well as two finger rotation + and two finger parallel vertical sliding to tilt the map. + + A MapGestureArea is automatically created with a new Map and available with + the \l{Map::gesture}{gesture} property. This is the only way + to create a MapGestureArea, and once created this way cannot be destroyed + without its parent Map. + + The two most commonly used properties of the MapGestureArea are the \l enabled + and \l acceptedGestures properties. Both of these must be set before a + MapGestureArea will have any effect upon interaction with the Map. + The \l flickDeceleration property controls how quickly the map pan slows after contact + is released while panning the map. + + \section2 Performance + + The MapGestureArea, when enabled, must process all incoming touch events in + order to track the shape and size of the "pinch". The overhead added on + touch events can be considered constant time. + + \section2 Example Usage + + The following example enables the pinch and pan gestures on the map, but not flicking. So the + map scrolling will halt immediately on releasing the mouse button / touch. + + \code + Map { + gesture.enabled: true + gesture.acceptedGestures: MapGestureArea.PinchGesture | MapGestureArea.PanGesture + } + \endcode + + \ingroup qml-QtLocation5-maps + \since Qt Location 5.0 +*/ + +/*! + \qmlproperty bool QtLocation::MapGestureArea::enabled + + This property holds whether the gestures are enabled. +*/ + +/*! + \qmlproperty bool QtLocation::MapGestureArea::pinchActive + + This read-only property holds whether the pinch gesture is active. +*/ + +/*! + \qmlproperty bool QtLocation::MapGestureArea::panActive + + This read-only property holds whether the pan gesture is active. + + \note Change notifications for this property were introduced in Qt 5.5. +*/ + +/*! + \qmlproperty bool QtLocation::MapGestureArea::rotationActive + + This read-only property holds whether the two-finger rotation gesture is active. + + \since Qt Location 5.9 +*/ + +/*! + \qmlproperty bool QtLocation::MapGestureArea::tiltActive + + This read-only property holds whether the two-finger tilt gesture is active. + + \since Qt Location 5.9 +*/ + +/*! + \qmlproperty real QtLocation::MapGestureArea::maximumZoomLevelChange + + This property holds the maximum zoom level change per pinch, essentially + meant to be used for setting the zoom sensitivity. + + It is an indicative measure calculated from the dimensions of the + map area, roughly corresponding how much zoom level could change with + maximum pinch zoom. Default value is 4.0, maximum value is 10.0 +*/ + +/*! + \qmlproperty real MapGestureArea::flickDeceleration + + This property holds the rate at which a flick will decelerate. + + The default value is 2500. +*/ + +/*! + \qmlsignal QtLocation::MapGestureArea::pinchStarted(PinchEvent event) + + This signal is emitted when a pinch gesture is started. + + The corresponding handler is \c onPinchStarted. + + \sa pinchUpdated, pinchFinished +*/ + +/*! + \qmlsignal QtLocation::MapGestureArea::pinchUpdated(PinchEvent event) + + This signal is emitted as the user's fingers move across the map, + after the \l pinchStarted signal is emitted. + + The corresponding handler is \c onPinchUpdated. + + \sa pinchStarted, pinchFinished +*/ + +/*! + \qmlsignal QtLocation::MapGestureArea::pinchFinished(PinchEvent event) + + This signal is emitted at the end of a pinch gesture. + + The corresponding handler is \c onPinchFinished. + + \sa pinchStarted, pinchUpdated +*/ + +/*! + \qmlsignal QtLocation::MapGestureArea::panStarted() + + This signal is emitted when the map begins to move due to user + interaction. Typically this means that the user is dragging a finger - + or a mouse with one of more mouse buttons pressed - on the map. + + The corresponding handler is \c onPanStarted. +*/ + +/*! + \qmlsignal QtLocation::MapGestureArea::panFinished() + + This signal is emitted when the map stops moving due to user + interaction. If a flick was generated, this signal is + emitted before flick starts. If a flick was not + generated, this signal is emitted when the + user stops dragging - that is a mouse or touch release. + + The corresponding handler is \c onPanFinished. + +*/ + +/*! + \qmlsignal QtLocation::MapGestureArea::flickStarted() + + This signal is emitted when the map is flicked. A flick + starts from the point where the mouse or touch was released, + while still in motion. + + The corresponding handler is \c onFlichStarted. +*/ + +/*! + \qmlsignal QtLocation::MapGestureArea::flickFinished() + + This signal is emitted when the map stops moving due to a flick. + + The corresponding handler is \c onFlickFinished. +*/ + +/*! + \qmlsignal QtLocation::MapGestureArea::rotationStarted(PinchEvent event) + + This signal is emitted when a two-finger rotation gesture is started. + + The corresponding handler is \c onRotationStarted. + + \sa rotationUpdated, rotationFinished + + \since Qt Location 5.9 +*/ + +/*! + \qmlsignal QtLocation::MapGestureArea::rotationUpdated(PinchEvent event) + + This signal is emitted as the user's fingers move across the map, + after the \l rotationStarted signal is emitted. + + The corresponding handler is \c onRotationUpdated. + + \sa rotationStarted, rotationFinished + + \since Qt Location 5.9 +*/ + +/*! + \qmlsignal QtLocation::MapGestureArea::rotationFinished(PinchEvent event) + + This signal is emitted at the end of a two-finger rotation gesture. + + The corresponding handler is \c onRotationFinished. + + \sa rotationStarted, rotationUpdated + + \since Qt Location 5.9 +*/ + +/*! + \qmlsignal QtLocation::MapGestureArea::tiltStarted(PinchEvent event) + + This signal is emitted when a two-finger tilt gesture is started. + + The corresponding handler is \c onTiltStarted. + + \sa tiltUpdated, tiltFinished + + \since Qt Location 5.9 +*/ + +/*! + \qmlsignal QtLocation::MapGestureArea::tiltUpdated(PinchEvent event) + + This signal is emitted as the user's fingers move across the map, + after the \l tiltStarted signal is emitted. + + The corresponding handler is \c onTiltUpdated. + + \sa tiltStarted, tiltFinished + + \since Qt Location 5.9 +*/ + +/*! + \qmlsignal QtLocation::MapGestureArea::tiltFinished(PinchEvent event) + + This signal is emitted at the end of a two-finger tilt gesture. + + The corresponding handler is \c onTiltFinished. + + \sa tiltStarted, tiltUpdated + + \since Qt Location 5.9 +*/ + +QQuickGeoMapGestureArea::QQuickGeoMapGestureArea(QDeclarativeGeoMap *map) + : QQuickItem(map), + m_map(0), + m_declarativeMap(map), + m_enabled(true), + m_acceptedGestures(PinchGesture | PanGesture | FlickGesture | RotationGesture | TiltGesture), + m_preventStealing(false) +{ + m_touchPointState = touchPoints0; + m_pinchState = pinchInactive; + m_flickState = flickInactive; + m_rotationState = rotationInactive; + m_tiltState = tiltInactive; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::setMap(QPointer<QGeoMap> map) +{ + if (m_map || !map) + return; + + m_map = map; + m_flick.m_animation = new QQuickGeoCoordinateAnimation(this); + m_flick.m_animation->setTargetObject(m_declarativeMap); + m_flick.m_animation->setProperty(QStringLiteral("center")); + m_flick.m_animation->setEasing(QEasingCurve(QEasingCurve::OutQuad)); + connect(m_flick.m_animation, &QQuickAbstractAnimation::stopped, this, &QQuickGeoMapGestureArea::handleFlickAnimationStopped); +} + +/*! + \qmlproperty bool QtQuick::MapGestureArea::preventStealing + This property holds whether the mouse events may be stolen from this + MapGestureArea. + + If a Map is placed within an item that filters child mouse + and touch events, such as Flickable, the mouse and touch events + may be stolen from the MapGestureArea if a gesture is recognized + by the parent item, e.g. a flick gesture. If preventStealing is + set to true, no item will steal the mouse and touch events. + + Note that setting preventStealing to true once an item has started + stealing events will have no effect until the next press event. + + By default this property is false. +*/ + +bool QQuickGeoMapGestureArea::preventStealing() const +{ + return m_preventStealing; +} + +void QQuickGeoMapGestureArea::setPreventStealing(bool prevent) +{ + if (prevent != m_preventStealing) { + m_preventStealing = prevent; + m_declarativeMap->setKeepMouseGrab(m_preventStealing && m_enabled); + m_declarativeMap->setKeepTouchGrab(m_preventStealing && m_enabled); + emit preventStealingChanged(); + } +} + +QQuickGeoMapGestureArea::~QQuickGeoMapGestureArea() +{ +} + +/*! + \qmlproperty enumeration QtLocation::MapGestureArea::acceptedGestures + + This property holds the gestures that will be active. By default + the zoom, pan and flick gestures are enabled. + + \list + \li MapGestureArea.NoGesture - Don't support any additional gestures (value: 0x0000). + \li MapGestureArea.PinchGesture - Support the map pinch gesture (value: 0x0001). + \li MapGestureArea.PanGesture - Support the map pan gesture (value: 0x0002). + \li MapGestureArea.FlickGesture - Support the map flick gesture (value: 0x0004). + \li MapGestureArea.RotationGesture - Support the map rotation gesture (value: 0x0008). + \li MapGestureArea.TiltGesture - Support the map tilt gesture (value: 0x0010). + \endlist +*/ + +QQuickGeoMapGestureArea::AcceptedGestures QQuickGeoMapGestureArea::acceptedGestures() const +{ + return m_acceptedGestures; +} + + +void QQuickGeoMapGestureArea::setAcceptedGestures(AcceptedGestures acceptedGestures) +{ + if (acceptedGestures == m_acceptedGestures) + return; + m_acceptedGestures = acceptedGestures; + + if (enabled()) { + setPanEnabled(acceptedGestures & PanGesture); + setFlickEnabled(acceptedGestures & FlickGesture); + setPinchEnabled(acceptedGestures & PinchGesture); + setRotationEnabled(acceptedGestures & RotationGesture); + setTiltEnabled(acceptedGestures & TiltGesture); + } + + emit acceptedGesturesChanged(); +} + +/*! + \internal +*/ +bool QQuickGeoMapGestureArea::isPinchActive() const +{ + return m_pinchState == pinchActive; +} + +/*! + \internal +*/ +bool QQuickGeoMapGestureArea::isRotationActive() const +{ + return m_rotationState == rotationActive; +} + +/*! + \internal +*/ +bool QQuickGeoMapGestureArea::isTiltActive() const +{ + return m_tiltState == tiltActive; +} + +/*! + \internal +*/ +bool QQuickGeoMapGestureArea::isPanActive() const +{ + return m_flickState == panActive || m_flickState == flickActive; +} + +/*! + \internal +*/ +bool QQuickGeoMapGestureArea::enabled() const +{ + return m_enabled; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::setEnabled(bool enabled) +{ + if (enabled == m_enabled) + return; + m_enabled = enabled; + + if (enabled) { + setPanEnabled(m_acceptedGestures & PanGesture); + setFlickEnabled(m_acceptedGestures & FlickGesture); + setPinchEnabled(m_acceptedGestures & PinchGesture); + setRotationEnabled(m_acceptedGestures & RotationGesture); + setTiltEnabled(m_acceptedGestures & TiltGesture); + } else { + setPanEnabled(false); + setFlickEnabled(false); + setPinchEnabled(false); + setRotationEnabled(false); + setTiltEnabled(false); + } + + emit enabledChanged(); +} + +/*! + \internal +*/ +bool QQuickGeoMapGestureArea::pinchEnabled() const +{ + return m_pinch.m_pinchEnabled; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::setPinchEnabled(bool enabled) +{ + m_pinch.m_pinchEnabled = enabled; +} + +/*! + \internal +*/ +bool QQuickGeoMapGestureArea::rotationEnabled() const +{ + return m_pinch.m_rotationEnabled; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::setRotationEnabled(bool enabled) +{ + m_pinch.m_rotationEnabled = enabled; +} + +/*! + \internal +*/ +bool QQuickGeoMapGestureArea::tiltEnabled() const +{ + return m_pinch.m_tiltEnabled; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::setTiltEnabled(bool enabled) +{ + m_pinch.m_tiltEnabled = enabled; +} + +/*! + \internal +*/ +bool QQuickGeoMapGestureArea::panEnabled() const +{ + return m_flick.m_panEnabled; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::setPanEnabled(bool enabled) +{ + if (enabled == m_flick.m_panEnabled) + return; + m_flick.m_panEnabled = enabled; + + // unlike the pinch, the pan existing functionality is to stop immediately + if (!enabled) { + stopPan(); + m_flickState = flickInactive; + } +} + +/*! + \internal +*/ +bool QQuickGeoMapGestureArea::flickEnabled() const +{ + return m_flick.m_flickEnabled; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::setFlickEnabled(bool enabled) +{ + if (enabled == m_flick.m_flickEnabled) + return; + m_flick.m_flickEnabled = enabled; + // unlike the pinch, the flick existing functionality is to stop immediately + if (!enabled) { + bool stateActive = (m_flickState != flickInactive); + stopFlick(); + if (stateActive) { + if (m_flick.m_panEnabled) + m_flickState = panActive; + else + m_flickState = flickInactive; + } + } +} + +/*! + \internal + Used internally to set the minimum zoom level of the gesture area. + The caller is responsible to only send values that are valid + for the map plugin. Negative values are ignored. + */ +void QQuickGeoMapGestureArea::setMinimumZoomLevel(qreal min) +{ + // TODO: remove m_zoom.m_minimum and m_maximum and use m_declarativeMap directly instead. + if (min >= 0) + m_pinch.m_zoom.m_minimum = min; +} + +/*! + \internal + */ +qreal QQuickGeoMapGestureArea::minimumZoomLevel() const +{ + return m_pinch.m_zoom.m_minimum; +} + +/*! + \internal + Used internally to set the maximum zoom level of the gesture area. + The caller is responsible to only send values that are valid + for the map plugin. Negative values are ignored. + */ +void QQuickGeoMapGestureArea::setMaximumZoomLevel(qreal max) +{ + if (max >= 0) + m_pinch.m_zoom.m_maximum = max; +} + +/*! + \internal + */ +qreal QQuickGeoMapGestureArea::maximumZoomLevel() const +{ + return m_pinch.m_zoom.m_maximum; +} + +/*! + \internal +*/ +qreal QQuickGeoMapGestureArea::maximumZoomLevelChange() const +{ + return m_pinch.m_zoom.maximumChange; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::setMaximumZoomLevelChange(qreal maxChange) +{ + if (maxChange == m_pinch.m_zoom.maximumChange || maxChange < 0.1 || maxChange > 10.0) + return; + m_pinch.m_zoom.maximumChange = maxChange; + emit maximumZoomLevelChangeChanged(); +} + +/*! + \internal +*/ +qreal QQuickGeoMapGestureArea::flickDeceleration() const +{ + return m_flick.m_deceleration; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::setFlickDeceleration(qreal deceleration) +{ + if (deceleration < QML_MAP_FLICK_MINIMUMDECELERATION) + deceleration = QML_MAP_FLICK_MINIMUMDECELERATION; + else if (deceleration > QML_MAP_FLICK_MAXIMUMDECELERATION) + deceleration = QML_MAP_FLICK_MAXIMUMDECELERATION; + if (deceleration == m_flick.m_deceleration) + return; + m_flick.m_deceleration = deceleration; + emit flickDecelerationChanged(); +} + +/*! + \internal +*/ +QTouchEvent::TouchPoint* createTouchPointFromMouseEvent(QMouseEvent *event, Qt::TouchPointState state) +{ + // this is only partially filled. But since it is only partially used it works + // more robust would be to store a list of QPointFs rather than TouchPoints + QTouchEvent::TouchPoint* newPoint = new QTouchEvent::TouchPoint(); + newPoint->setPos(event->localPos()); + newPoint->setScenePos(event->windowPos()); + newPoint->setScreenPos(event->screenPos()); + newPoint->setState(state); + newPoint->setId(0); + return newPoint; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::handleMousePressEvent(QMouseEvent *event) +{ + m_mousePoint.reset(createTouchPointFromMouseEvent(event, Qt::TouchPointPressed)); + if (m_touchPoints.isEmpty()) update(); + event->accept(); +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::handleMouseMoveEvent(QMouseEvent *event) +{ + m_mousePoint.reset(createTouchPointFromMouseEvent(event, Qt::TouchPointMoved)); + if (m_touchPoints.isEmpty()) update(); + event->accept(); +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::handleMouseReleaseEvent(QMouseEvent *event) +{ + if (!m_mousePoint.isNull()) { + //this looks super ugly , however is required in case we do not get synthesized MouseReleaseEvent + //and we reset the point already in handleTouchUngrabEvent + m_mousePoint.reset(createTouchPointFromMouseEvent(event, Qt::TouchPointReleased)); + if (m_touchPoints.isEmpty()) update(); + } + event->accept(); +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::handleMouseUngrabEvent() +{ + + if (m_touchPoints.isEmpty() && !m_mousePoint.isNull()) { + m_mousePoint.reset(); + update(); + } else { + m_mousePoint.reset(); + } +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::handleTouchUngrabEvent() +{ + m_touchPoints.clear(); + //this is needed since in some cases mouse release is not delivered + //(second touch point breaks mouse synthesized events) + m_mousePoint.reset(); + update(); +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::handleTouchEvent(QTouchEvent *event) +{ + m_touchPoints.clear(); + m_mousePoint.reset(); + + for (int i = 0; i < event->touchPoints().count(); ++i) { + auto point = event->touchPoints().at(i); + if (point.state() != Qt::TouchPointReleased) + m_touchPoints << point; + } + if (event->touchPoints().count() >= 2) + event->accept(); + else + event->ignore(); + update(); +} + +void QQuickGeoMapGestureArea::handleWheelEvent(QWheelEvent *event) +{ + if (!m_map) + return; + + const QGeoCoordinate &wheelGeoPos = m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(event->posF()), false); + const QPointF &preZoomPoint = event->posF(); + + const double zoomLevelDelta = event->angleDelta().y() * qreal(0.001); + // Gesture area should always honor maxZL, but Map might not. + m_declarativeMap->setZoomLevel(qMin<qreal>(m_declarativeMap->zoomLevel() + zoomLevelDelta, maximumZoomLevel())); + const QPointF &postZoomPoint = m_map->geoProjection().coordinateToItemPosition(wheelGeoPos, false).toPointF(); + + if (preZoomPoint != postZoomPoint) // need to re-anchor the wheel geoPos to the event position + m_declarativeMap->setCenter(anchorCoordinateToPoint(*m_map, wheelGeoPos, preZoomPoint)); + + event->accept(); +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::clearTouchData() +{ + m_flickVector = QVector2D(); + m_touchPointsCentroid.setX(0); + m_touchPointsCentroid.setY(0); + m_touchCenterCoord.setLongitude(0); + m_touchCenterCoord.setLatitude(0); + m_startCoord.setLongitude(0); + m_startCoord.setLatitude(0); +} + + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::updateFlickParameters(const QPointF &pos) +{ + // Take velocity samples every sufficient period of time, used later to determine the flick + // duration and speed (when mouse is released). + qreal elapsed = qreal(m_lastPosTime.elapsed()); + + if (elapsed >= QML_MAP_FLICK_VELOCITY_SAMPLE_PERIOD) { + elapsed /= 1000.; + qreal vel = distanceBetweenTouchPoints(pos, m_lastPos) / elapsed; + m_flickVector = (QVector2D(pos) - QVector2D(m_lastPos)).normalized(); + m_flickVector *= qBound<qreal>(-m_flick.m_maxVelocity, vel, m_flick.m_maxVelocity); + + m_lastPos = pos; + m_lastPosTime.restart(); + } +} + +void QQuickGeoMapGestureArea::setTouchPointState(const QQuickGeoMapGestureArea::TouchPointState state) +{ + m_touchPointState = state; +} + +void QQuickGeoMapGestureArea::setFlickState(const QQuickGeoMapGestureArea::FlickState state) +{ + m_flickState = state; +} + +void QQuickGeoMapGestureArea::setTiltState(const QQuickGeoMapGestureArea::TiltState state) +{ + m_tiltState = state; +} + +void QQuickGeoMapGestureArea::setRotationState(const QQuickGeoMapGestureArea::RotationState state) +{ + m_rotationState = state; +} + +void QQuickGeoMapGestureArea::setPinchState(const QQuickGeoMapGestureArea::PinchState state) +{ + m_pinchState = state; +} + +/*! + \internal +*/ + +bool QQuickGeoMapGestureArea::isActive() const +{ + return isPanActive() || isPinchActive() || isRotationActive() || isTiltActive(); +} + +/*! + \internal +*/ +// simplify the gestures by using a state-machine format (easy to move to a future state machine) +void QQuickGeoMapGestureArea::update() +{ + if (!m_map) + return; + // First state machine is for the number of touch points + + //combine touch with mouse event + m_allPoints.clear(); + m_allPoints << m_touchPoints; + if (m_allPoints.isEmpty() && !m_mousePoint.isNull()) + m_allPoints << *m_mousePoint.data(); + + touchPointStateMachine(); + + // Parallel state machine for tilt. Tilt goes first as it blocks anything else, when started. + // But tilting can also only start if nothing else is active. + if (isTiltActive() || m_pinch.m_tiltEnabled) + tiltStateMachine(); + + // Parallel state machine for pinch + if (isPinchActive() || m_pinch.m_pinchEnabled) + pinchStateMachine(); + + // Parallel state machine for rotation. + if (isRotationActive() || m_pinch.m_rotationEnabled) + rotationStateMachine(); + + // Parallel state machine for pan (since you can pan at the same time as pinching) + // The stopPan function ensures that pan stops immediately when disabled, + // but the isPanActive() below allows pan continue its current gesture if you disable + // the whole gesture. + // Pan goes last because it does reanchoring in updatePan() which makes the map + // properly rotate around the touch point centroid. + if (isPanActive() || m_flick.m_flickEnabled || m_flick.m_panEnabled) + panStateMachine(); +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::touchPointStateMachine() +{ + // Transitions: + switch (m_touchPointState) { + case touchPoints0: + if (m_allPoints.count() == 1) { + clearTouchData(); + startOneTouchPoint(); + setTouchPointState(touchPoints1); + } else if (m_allPoints.count() >= 2) { + clearTouchData(); + startTwoTouchPoints(); + setTouchPointState(touchPoints2); + } + break; + case touchPoints1: + if (m_allPoints.count() == 0) { + setTouchPointState(touchPoints0); + } else if (m_allPoints.count() == 2) { + m_touchCenterCoord = m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(m_touchPointsCentroid), false); + startTwoTouchPoints(); + setTouchPointState(touchPoints2); + } + break; + case touchPoints2: + if (m_allPoints.count() == 0) { + setTouchPointState(touchPoints0); + } else if (m_allPoints.count() == 1) { + m_touchCenterCoord = m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(m_touchPointsCentroid), false); + startOneTouchPoint(); + setTouchPointState(touchPoints1); + } + break; + }; + + // Update + switch (m_touchPointState) { + case touchPoints0: + break; // do nothing if no touch points down + case touchPoints1: + updateOneTouchPoint(); + break; + case touchPoints2: + updateTwoTouchPoints(); + break; + } +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::startOneTouchPoint() +{ + m_sceneStartPoint1 = mapFromScene(m_allPoints.at(0).scenePos()); + m_lastPos = m_sceneStartPoint1; + m_lastPosTime.start(); + QGeoCoordinate startCoord = m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(m_sceneStartPoint1), false); + // ensures a smooth transition for panning + m_startCoord.setLongitude(m_startCoord.longitude() + startCoord.longitude() - + m_touchCenterCoord.longitude()); + m_startCoord.setLatitude(m_startCoord.latitude() + startCoord.latitude() - + m_touchCenterCoord.latitude()); +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::updateOneTouchPoint() +{ + m_touchPointsCentroid = mapFromScene(m_allPoints.at(0).scenePos()); + updateFlickParameters(m_touchPointsCentroid); +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::startTwoTouchPoints() +{ + m_sceneStartPoint1 = mapFromScene(m_allPoints.at(0).scenePos()); + m_sceneStartPoint2 = mapFromScene(m_allPoints.at(1).scenePos()); + QPointF startPos = (m_sceneStartPoint1 + m_sceneStartPoint2) * 0.5; + m_lastPos = startPos; + m_lastPosTime.start(); + QGeoCoordinate startCoord = m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(startPos), false); + m_startCoord.setLongitude(m_startCoord.longitude() + startCoord.longitude() - + m_touchCenterCoord.longitude()); + m_startCoord.setLatitude(m_startCoord.latitude() + startCoord.latitude() - + m_touchCenterCoord.latitude()); + m_twoTouchAngleStart = touchAngle(m_sceneStartPoint1, m_sceneStartPoint2); // Initial angle used for calculating rotation + m_distanceBetweenTouchPointsStart = distanceBetweenTouchPoints(m_sceneStartPoint1, m_sceneStartPoint2); + m_twoTouchPointsCentroidStart = (m_sceneStartPoint1 + m_sceneStartPoint2) / 2; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::updateTwoTouchPoints() +{ + QPointF p1 = mapFromScene(m_allPoints.at(0).scenePos()); + QPointF p2 = mapFromScene(m_allPoints.at(1).scenePos()); + m_distanceBetweenTouchPoints = distanceBetweenTouchPoints(p1, p2); + m_touchPointsCentroid = (p1 + p2) / 2; + updateFlickParameters(m_touchPointsCentroid); + + m_twoTouchAngle = touchAngle(p1, p2); +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::tiltStateMachine() +{ + TiltState lastState = m_tiltState; + // Transitions: + switch (m_tiltState) { + case tiltInactive: + if (m_allPoints.count() >= 2) { + if (!isRotationActive() && !isPinchActive() && canStartTilt()) { // only gesture that can be overridden: pan/flick + m_declarativeMap->setKeepMouseGrab(true); + m_declarativeMap->setKeepTouchGrab(true); + startTilt(); + setTiltState(tiltActive); + } else { + setTiltState(tiltInactiveTwoPoints); + } + } + break; + case tiltInactiveTwoPoints: + if (m_allPoints.count() <= 1) { + setTiltState(tiltInactive); + } else { + if (!isRotationActive() && !isPinchActive() && canStartTilt()) { // only gesture that can be overridden: pan/flick + m_declarativeMap->setKeepMouseGrab(true); + m_declarativeMap->setKeepTouchGrab(true); + startTilt(); + setTiltState(tiltActive); + } + } + break; + case tiltActive: + if (m_allPoints.count() <= 1) { + setTiltState(tiltInactive); + m_declarativeMap->setKeepMouseGrab(m_preventStealing); + m_declarativeMap->setKeepTouchGrab(m_preventStealing); + endTilt(); + } + break; + } + // This line implements an exclusive state machine, where the transitions and updates don't + // happen on the same frame + if (m_tiltState != lastState) { + emit tiltActiveChanged(); + return; + } + + // Update + switch (m_tiltState) { + case tiltInactive: + case tiltInactiveTwoPoints: + break; // do nothing + case tiltActive: + updateTilt(); + break; + } +} + +bool validateTouchAngleForTilting(const qreal angle) +{ + return ((qAbs(angle) - 180.0) < MaximumParallelPosition) || (qAbs(angle) < MaximumParallelPosition); +} + +/*! + \internal +*/ +bool QQuickGeoMapGestureArea::canStartTilt() +{ + if (m_allPoints.count() >= 2) { + QPointF p1 = mapFromScene(m_allPoints.at(0).scenePos()); + QPointF p2 = mapFromScene(m_allPoints.at(1).scenePos()); + if (validateTouchAngleForTilting(m_twoTouchAngle) + && movingParallelVertical(m_sceneStartPoint1, p1, m_sceneStartPoint2, p2) + && qAbs(m_twoTouchPointsCentroidStart.y() - m_touchPointsCentroid.y()) > MinimumPanToTiltDelta) { + m_pinch.m_event.setCenter(mapFromScene(m_touchPointsCentroid)); + m_pinch.m_event.setAngle(m_twoTouchAngle); + m_pinch.m_event.setPoint1(p1); + m_pinch.m_event.setPoint2(p2); + m_pinch.m_event.setPointCount(m_allPoints.count()); + m_pinch.m_event.setAccepted(true); + emit tiltStarted(&m_pinch.m_event); + return true; + } + } + return false; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::startTilt() +{ + if (isPanActive()) { + stopPan(); + setFlickState(flickInactive); + } + + m_pinch.m_tilt.m_startTouchCentroid = m_touchPointsCentroid; + m_pinch.m_tilt.m_startTilt = m_declarativeMap->tilt(); +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::updateTilt() +{ + // Calculate the new tilt + qreal verticalDisplacement = (m_touchPointsCentroid - m_pinch.m_tilt.m_startTouchCentroid).y(); + + // Approach: 10pixel = 1 degree. + qreal tilt = verticalDisplacement / 10.0; + qreal newTilt = m_pinch.m_tilt.m_startTilt - tilt; + m_declarativeMap->setTilt(newTilt); + + m_pinch.m_event.setCenter(mapFromScene(m_touchPointsCentroid)); + m_pinch.m_event.setAngle(m_twoTouchAngle); + m_pinch.m_lastPoint1 = mapFromScene(m_allPoints.at(0).scenePos()); + m_pinch.m_lastPoint2 = mapFromScene(m_allPoints.at(1).scenePos()); + m_pinch.m_event.setPoint1(m_pinch.m_lastPoint1); + m_pinch.m_event.setPoint2(m_pinch.m_lastPoint2); + m_pinch.m_event.setPointCount(m_allPoints.count()); + m_pinch.m_event.setAccepted(true); + + emit tiltUpdated(&m_pinch.m_event); +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::endTilt() +{ + QPointF p1 = mapFromScene(m_pinch.m_lastPoint1); + QPointF p2 = mapFromScene(m_pinch.m_lastPoint2); + m_pinch.m_event.setCenter((p1 + p2) / 2); + m_pinch.m_event.setAngle(m_pinch.m_lastAngle); + m_pinch.m_event.setPoint1(p1); + m_pinch.m_event.setPoint2(p2); + m_pinch.m_event.setAccepted(true); + m_pinch.m_event.setPointCount(0); + emit tiltFinished(&m_pinch.m_event); +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::rotationStateMachine() +{ + RotationState lastState = m_rotationState; + // Transitions: + switch (m_rotationState) { + case rotationInactive: + if (m_allPoints.count() >= 2) { + if (!isTiltActive() && canStartRotation()) { + m_declarativeMap->setKeepMouseGrab(true); + m_declarativeMap->setKeepTouchGrab(true); + startRotation(); + setRotationState(rotationActive); + } else { + setRotationState(rotationInactiveTwoPoints); + } + } + break; + case rotationInactiveTwoPoints: + if (m_allPoints.count() <= 1) { + setRotationState(rotationInactive); + } else { + if (!isTiltActive() && canStartRotation()) { + m_declarativeMap->setKeepMouseGrab(true); + m_declarativeMap->setKeepTouchGrab(true); + startRotation(); + setRotationState(rotationActive); + } + } + break; + case rotationActive: + if (m_allPoints.count() <= 1) { + setRotationState(rotationInactive); + m_declarativeMap->setKeepMouseGrab(m_preventStealing); + m_declarativeMap->setKeepTouchGrab(m_preventStealing); + endRotation(); + } + break; + } + // This line implements an exclusive state machine, where the transitions and updates don't + // happen on the same frame + if (m_rotationState != lastState) { + emit rotationActiveChanged(); + return; + } + + // Update + switch (m_rotationState) { + case rotationInactive: + case rotationInactiveTwoPoints: + break; // do nothing + case rotationActive: + updateRotation(); + break; + } +} + +/*! + \internal +*/ +bool QQuickGeoMapGestureArea::canStartRotation() +{ + if (m_allPoints.count() >= 2) { + QPointF p1 = mapFromScene(m_allPoints.at(0).scenePos()); + QPointF p2 = mapFromScene(m_allPoints.at(1).scenePos()); + if (pointDragged(m_sceneStartPoint1, p1) || pointDragged(m_sceneStartPoint2, p2)) { + qreal delta = angleDelta(m_twoTouchAngleStart, m_twoTouchAngle); + if (qAbs(delta) < MinimumRotationStartingAngle) { + return false; + } + m_pinch.m_event.setCenter(mapFromScene(m_touchPointsCentroid)); + m_pinch.m_event.setAngle(m_twoTouchAngle); + m_pinch.m_event.setPoint1(p1); + m_pinch.m_event.setPoint2(p2); + m_pinch.m_event.setPointCount(m_allPoints.count()); + m_pinch.m_event.setAccepted(true); + emit rotationStarted(&m_pinch.m_event); + return m_pinch.m_event.accepted(); + } + } + return false; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::startRotation() +{ + m_pinch.m_rotation.m_startBearing = m_declarativeMap->bearing(); + m_pinch.m_rotation.m_previousTouchAngle = m_twoTouchAngle; + m_pinch.m_rotation.m_totalAngle = 0.0; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::updateRotation() +{ + // Calculate the new bearing + qreal angle = angleDelta(m_pinch.m_rotation.m_previousTouchAngle, m_twoTouchAngle); + if (qAbs(angle) < 0.2) // avoiding too many updates + return; + + m_pinch.m_rotation.m_previousTouchAngle = m_twoTouchAngle; + m_pinch.m_rotation.m_totalAngle += angle; + qreal newBearing = m_pinch.m_rotation.m_startBearing - m_pinch.m_rotation.m_totalAngle; + m_declarativeMap->setBearing(newBearing); + + m_pinch.m_event.setCenter(mapFromScene(m_touchPointsCentroid)); + m_pinch.m_event.setAngle(m_twoTouchAngle); + m_pinch.m_lastPoint1 = mapFromScene(m_allPoints.at(0).scenePos()); + m_pinch.m_lastPoint2 = mapFromScene(m_allPoints.at(1).scenePos()); + m_pinch.m_event.setPoint1(m_pinch.m_lastPoint1); + m_pinch.m_event.setPoint2(m_pinch.m_lastPoint2); + m_pinch.m_event.setPointCount(m_allPoints.count()); + m_pinch.m_event.setAccepted(true); + + emit rotationUpdated(&m_pinch.m_event); +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::endRotation() +{ + QPointF p1 = mapFromScene(m_pinch.m_lastPoint1); + QPointF p2 = mapFromScene(m_pinch.m_lastPoint2); + m_pinch.m_event.setCenter((p1 + p2) / 2); + m_pinch.m_event.setAngle(m_pinch.m_lastAngle); + m_pinch.m_event.setPoint1(p1); + m_pinch.m_event.setPoint2(p2); + m_pinch.m_event.setAccepted(true); + m_pinch.m_event.setPointCount(0); + emit rotationFinished(&m_pinch.m_event); +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::pinchStateMachine() +{ + PinchState lastState = m_pinchState; + // Transitions: + switch (m_pinchState) { + case pinchInactive: + if (m_allPoints.count() >= 2) { + if (!isTiltActive() && canStartPinch()) { + m_declarativeMap->setKeepMouseGrab(true); + m_declarativeMap->setKeepTouchGrab(true); + startPinch(); + setPinchState(pinchActive); + } else { + setPinchState(pinchInactiveTwoPoints); + } + } + break; + case pinchInactiveTwoPoints: + if (m_allPoints.count() <= 1) { + setPinchState(pinchInactive); + } else { + if (!isTiltActive() && canStartPinch()) { + m_declarativeMap->setKeepMouseGrab(true); + m_declarativeMap->setKeepTouchGrab(true); + startPinch(); + setPinchState(pinchActive); + } + } + break; + case pinchActive: + if (m_allPoints.count() <= 1) { // Once started, pinch goes off only when finger(s) are release + setPinchState(pinchInactive); + m_declarativeMap->setKeepMouseGrab(m_preventStealing); + m_declarativeMap->setKeepTouchGrab(m_preventStealing); + endPinch(); + } + break; + } + // This line implements an exclusive state machine, where the transitions and updates don't + // happen on the same frame + if (m_pinchState != lastState) { + emit pinchActiveChanged(); + return; + } + + // Update + switch (m_pinchState) { + case pinchInactive: + case pinchInactiveTwoPoints: + break; // do nothing + case pinchActive: + updatePinch(); + break; + } +} + +/*! + \internal +*/ +bool QQuickGeoMapGestureArea::canStartPinch() +{ + if (m_allPoints.count() >= 2) { + QPointF p1 = mapFromScene(m_allPoints.at(0).scenePos()); + QPointF p2 = mapFromScene(m_allPoints.at(1).scenePos()); + if (qAbs(m_distanceBetweenTouchPoints - m_distanceBetweenTouchPointsStart) > MinimumPinchDelta) { + m_pinch.m_event.setCenter(mapFromScene(m_touchPointsCentroid)); + m_pinch.m_event.setAngle(m_twoTouchAngle); + m_pinch.m_event.setPoint1(p1); + m_pinch.m_event.setPoint2(p2); + m_pinch.m_event.setPointCount(m_allPoints.count()); + m_pinch.m_event.setAccepted(true); + emit pinchStarted(&m_pinch.m_event); + return m_pinch.m_event.accepted(); + } + } + return false; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::startPinch() +{ + m_pinch.m_startDist = m_distanceBetweenTouchPoints; + m_pinch.m_zoom.m_previous = m_declarativeMap->zoomLevel(); + m_pinch.m_lastAngle = m_twoTouchAngle; + + m_pinch.m_lastPoint1 = mapFromScene(m_allPoints.at(0).scenePos()); + m_pinch.m_lastPoint2 = mapFromScene(m_allPoints.at(1).scenePos()); + + m_pinch.m_zoom.m_start = m_declarativeMap->zoomLevel(); +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::updatePinch() +{ + // Calculate the new zoom level if we have distance ( >= 2 touchpoints), otherwise stick with old. + qreal newZoomLevel = m_pinch.m_zoom.m_previous; + if (m_distanceBetweenTouchPoints) { + newZoomLevel = + // How much further/closer the current touchpoints are (in pixels) compared to pinch start + ((m_distanceBetweenTouchPoints - m_pinch.m_startDist) * + // How much one pixel corresponds in units of zoomlevel (and multiply by above delta) + (m_pinch.m_zoom.maximumChange / ((width() + height()) / 2))) + + // Add to starting zoom level. Sign of (dist-pinchstartdist) takes care of zoom in / out + m_pinch.m_zoom.m_start; + } + + m_pinch.m_event.setCenter(mapFromScene(m_touchPointsCentroid)); + m_pinch.m_event.setAngle(m_twoTouchAngle); + + m_pinch.m_lastPoint1 = mapFromScene(m_allPoints.at(0).scenePos()); + m_pinch.m_lastPoint2 = mapFromScene(m_allPoints.at(1).scenePos()); + m_pinch.m_event.setPoint1(m_pinch.m_lastPoint1); + m_pinch.m_event.setPoint2(m_pinch.m_lastPoint2); + m_pinch.m_event.setPointCount(m_allPoints.count()); + m_pinch.m_event.setAccepted(true); + + m_pinch.m_lastAngle = m_twoTouchAngle; + emit pinchUpdated(&m_pinch.m_event); + + if (m_acceptedGestures & PinchGesture) { + // Take maximum and minimumzoomlevel into account + qreal perPinchMinimumZoomLevel = qMax(m_pinch.m_zoom.m_start - m_pinch.m_zoom.maximumChange, m_pinch.m_zoom.m_minimum); + qreal perPinchMaximumZoomLevel = qMin(m_pinch.m_zoom.m_start + m_pinch.m_zoom.maximumChange, m_pinch.m_zoom.m_maximum); + newZoomLevel = qMin(qMax(perPinchMinimumZoomLevel, newZoomLevel), perPinchMaximumZoomLevel); + m_declarativeMap->setZoomLevel(qMin<qreal>(newZoomLevel, maximumZoomLevel())); + m_pinch.m_zoom.m_previous = newZoomLevel; + } +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::endPinch() +{ + QPointF p1 = mapFromScene(m_pinch.m_lastPoint1); + QPointF p2 = mapFromScene(m_pinch.m_lastPoint2); + m_pinch.m_event.setCenter((p1 + p2) / 2); + m_pinch.m_event.setAngle(m_pinch.m_lastAngle); + m_pinch.m_event.setPoint1(p1); + m_pinch.m_event.setPoint2(p2); + m_pinch.m_event.setAccepted(true); + m_pinch.m_event.setPointCount(0); + emit pinchFinished(&m_pinch.m_event); + m_pinch.m_startDist = 0; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::panStateMachine() +{ + FlickState lastState = m_flickState; + + // Transitions + switch (m_flickState) { + case flickInactive: + if (!isTiltActive() && canStartPan()) { + // Update startCoord_ to ensure smooth start for panning when going over startDragDistance + QGeoCoordinate newStartCoord = m_map->geoProjection().itemPositionToCoordinate(QDoubleVector2D(m_touchPointsCentroid), false); + m_startCoord.setLongitude(newStartCoord.longitude()); + m_startCoord.setLatitude(newStartCoord.latitude()); + m_declarativeMap->setKeepMouseGrab(true); + setFlickState(panActive); + } + break; + case panActive: + if (m_allPoints.count() == 0) { + if (!tryStartFlick()) + { + setFlickState(flickInactive); + // mark as inactive for use by camera + if (m_pinchState == pinchInactive && m_rotationState == rotationInactive && m_tiltState == tiltInactive) { + m_declarativeMap->setKeepMouseGrab(m_preventStealing); + m_map->prefetchData(); + } + emit panFinished(); + } else { + setFlickState(flickActive); + emit panFinished(); + emit flickStarted(); + } + } + break; + case flickActive: + if (m_allPoints.count() > 0) { // re touched before movement ended + stopFlick(); + m_declarativeMap->setKeepMouseGrab(true); + setFlickState(panActive); + } + break; + } + + if (m_flickState != lastState) + emit panActiveChanged(); + + // Update + switch (m_flickState) { + case flickInactive: // do nothing + break; + case panActive: + updatePan(); + // this ensures 'panStarted' occurs after the pan has actually started + if (lastState != panActive) + emit panStarted(); + break; + case flickActive: + break; + } +} +/*! + \internal +*/ +bool QQuickGeoMapGestureArea::canStartPan() +{ + if (m_allPoints.count() == 0 || (m_acceptedGestures & PanGesture) == 0) + return false; + + // Check if thresholds for normal panning are met. + // (normal panning vs flicking: flicking will start from mouse release event). + const int startDragDistance = qApp->styleHints()->startDragDistance() * 2; + QPointF p1 = mapFromScene(m_allPoints.at(0).scenePos()); + int dyFromPress = int(p1.y() - m_sceneStartPoint1.y()); + int dxFromPress = int(p1.x() - m_sceneStartPoint1.x()); + if ((qAbs(dyFromPress) >= startDragDistance || qAbs(dxFromPress) >= startDragDistance)) + return true; + return false; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::updatePan() +{ + QGeoCoordinate animationStartCoordinate = anchorCoordinateToPoint(*m_map, m_startCoord, m_touchPointsCentroid); + m_declarativeMap->setCenter(animationStartCoordinate); +} + +/*! + \internal +*/ +bool QQuickGeoMapGestureArea::tryStartFlick() +{ + if ((m_acceptedGestures & FlickGesture) == 0) + return false; + // if we drag then pause before release we should not cause a flick. + qreal flickSpeed = 0.0; + if (m_lastPosTime.elapsed() < QML_MAP_FLICK_VELOCITY_SAMPLE_PERIOD) + flickSpeed = m_flickVector.length(); + + int flickTime = 0; + int flickPixels = 0; + QVector2D flickVector; + + if (qAbs(flickSpeed) > MinimumFlickVelocity + && distanceBetweenTouchPoints(m_touchPointsCentroid, m_sceneStartPoint1) > FlickThreshold) { + qreal acceleration = m_flick.m_deceleration; + if ((flickSpeed > 0.0f) == (m_flick.m_deceleration > 0.0f)) + acceleration = acceleration * -1.0f; + flickTime = static_cast<int>(-1000 * flickSpeed / acceleration); + flickPixels = (flickTime * flickSpeed) / 2000.0; + flickVector = m_flickVector.normalized() * flickPixels; + } + + if (flickTime > 0) { + startFlick(flickVector.x(), flickVector.y(), flickTime); + return true; + } + return false; +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::startFlick(int dx, int dy, int timeMs) +{ + if (!m_flick.m_animation) + return; + if (timeMs < 0) + return; + + QGeoCoordinate animationStartCoordinate = m_declarativeMap->center(); + + if (m_flick.m_animation->isRunning()) + m_flick.m_animation->stop(); + QGeoCoordinate animationEndCoordinate = m_declarativeMap->center(); + m_flick.m_animation->setDuration(timeMs); + + QPointF delta(dx, dy); + QMatrix4x4 matBearing; + matBearing.rotate(m_map->cameraData().bearing(), 0, 0, 1); + delta = matBearing * delta; + + double zoom = pow(2.0, m_declarativeMap->zoomLevel()); + double longitude = animationStartCoordinate.longitude() - (delta.x() / zoom); + double latitude = animationStartCoordinate.latitude() + (delta.y() / zoom); + + if (delta.x() > 0) + m_flick.m_animation->setDirection(QQuickGeoCoordinateAnimation::East); + else + m_flick.m_animation->setDirection(QQuickGeoCoordinateAnimation::West); + + //keep animation in correct bounds + animationEndCoordinate.setLongitude(QLocationUtils::wrapLong(longitude)); + animationEndCoordinate.setLatitude(QLocationUtils::clipLat(latitude, QLocationUtils::mercatorMaxLatitude())); + + m_flick.m_animation->setFrom(animationStartCoordinate); + m_flick.m_animation->setTo(animationEndCoordinate); + m_flick.m_animation->start(); +} + +void QQuickGeoMapGestureArea::stopPan() +{ + if (m_flickState == flickActive) { + stopFlick(); + } else if (m_flickState == panActive) { + m_flickVector = QVector2D(); + setFlickState(flickInactive); + m_declarativeMap->setKeepMouseGrab(m_preventStealing); + emit panFinished(); + emit panActiveChanged(); + m_map->prefetchData(); + } +} + +/*! + \internal +*/ +void QQuickGeoMapGestureArea::stopFlick() +{ + if (!m_flick.m_animation) + return; + m_flickVector = QVector2D(); + if (m_flick.m_animation->isRunning()) + m_flick.m_animation->stop(); + else + handleFlickAnimationStopped(); +} + +void QQuickGeoMapGestureArea::handleFlickAnimationStopped() +{ + m_declarativeMap->setKeepMouseGrab(m_preventStealing); + if (m_flickState == flickActive) { + setFlickState(flickInactive); + emit flickFinished(); + emit panActiveChanged(); + m_map->prefetchData(); + } +} + +QT_END_NAMESPACE diff --git a/src/location/declarativemaps/qquickgeomapgesturearea_p.h b/src/location/declarativemaps/qquickgeomapgesturearea_p.h new file mode 100644 index 00000000..320c0fbe --- /dev/null +++ b/src/location/declarativemaps/qquickgeomapgesturearea_p.h @@ -0,0 +1,397 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKGEOMAPGESTUREAREA_P_H +#define QQUICKGEOMAPGESTUREAREA_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtLocation/private/qlocationglobal_p.h> + +#include <QtCore/QPointer> +#include <QtQuick/QQuickItem> +#include <QTouchEvent> +#include <QDebug> +#include <QElapsedTimer> +#include <QtPositioning/qgeocoordinate.h> +#include <QtGui/QVector2D> + +QT_BEGIN_NAMESPACE + +class QGraphicsSceneMouseEvent; +class QQuickGeoCoordinateAnimation; +class QDeclarativeGeoMap; +class QTouchEvent; +class QWheelEvent; +class QGeoMap; + +class Q_LOCATION_PRIVATE_EXPORT QGeoMapPinchEvent : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QPointF center READ center) + Q_PROPERTY(qreal angle READ angle) + Q_PROPERTY(QPointF point1 READ point1) + Q_PROPERTY(QPointF point2 READ point2) + Q_PROPERTY(int pointCount READ pointCount) + Q_PROPERTY(bool accepted READ accepted WRITE setAccepted) + +public: + QGeoMapPinchEvent(const QPointF ¢er, qreal angle, + const QPointF &point1, const QPointF &point2, + int pointCount = 0, bool accepted = true) + : QObject(), m_center(center), m_angle(angle), + m_point1(point1), m_point2(point2), + m_pointCount(pointCount), m_accepted(accepted) {} + QGeoMapPinchEvent() + : QObject(), + m_angle(0.0), + m_pointCount(0), + m_accepted(true) {} + + QPointF center() const { return m_center; } + void setCenter(const QPointF ¢er) { m_center = center; } + qreal angle() const { return m_angle; } + void setAngle(qreal angle) { m_angle = angle; } + QPointF point1() const { return m_point1; } + void setPoint1(const QPointF &p) { m_point1 = p; } + QPointF point2() const { return m_point2; } + void setPoint2(const QPointF &p) { m_point2 = p; } + int pointCount() const { return m_pointCount; } + void setPointCount(int count) { m_pointCount = count; } + bool accepted() const { return m_accepted; } + void setAccepted(bool a) { m_accepted = a; } + +private: + QPointF m_center; + qreal m_angle; + QPointF m_point1; + QPointF m_point2; + int m_pointCount; + bool m_accepted; +}; + +class Q_LOCATION_PRIVATE_EXPORT QQuickGeoMapGestureArea: public QQuickItem +{ + Q_OBJECT + Q_ENUMS(GeoMapGesture) + Q_FLAGS(AcceptedGestures) + + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) + Q_PROPERTY(bool pinchActive READ isPinchActive NOTIFY pinchActiveChanged) + Q_PROPERTY(bool panActive READ isPanActive NOTIFY panActiveChanged) + Q_PROPERTY(bool rotationActive READ isRotationActive NOTIFY rotationActiveChanged) + Q_PROPERTY(bool tiltActive READ isTiltActive NOTIFY tiltActiveChanged) + Q_PROPERTY(AcceptedGestures acceptedGestures READ acceptedGestures WRITE setAcceptedGestures NOTIFY acceptedGesturesChanged) + Q_PROPERTY(qreal maximumZoomLevelChange READ maximumZoomLevelChange WRITE setMaximumZoomLevelChange NOTIFY maximumZoomLevelChangeChanged) + Q_PROPERTY(qreal flickDeceleration READ flickDeceleration WRITE setFlickDeceleration NOTIFY flickDecelerationChanged) + Q_PROPERTY(bool preventStealing READ preventStealing WRITE setPreventStealing NOTIFY preventStealingChanged REVISION 1) + +public: + QQuickGeoMapGestureArea(QDeclarativeGeoMap *map); + ~QQuickGeoMapGestureArea(); + + enum GeoMapGesture { + NoGesture = 0x0000, + PinchGesture = 0x0001, + PanGesture = 0x0002, + FlickGesture = 0x0004, + RotationGesture = 0x0008, + TiltGesture = 0x0010 + }; + + Q_DECLARE_FLAGS(AcceptedGestures, GeoMapGesture) + + AcceptedGestures acceptedGestures() const; + void setAcceptedGestures(AcceptedGestures acceptedGestures); + + bool isPinchActive() const; + bool isRotationActive() const; + bool isTiltActive() const; + bool isPanActive() const; + bool isActive() const; + + bool enabled() const; + void setEnabled(bool enabled); + + qreal maximumZoomLevelChange() const; + void setMaximumZoomLevelChange(qreal maxChange); + + qreal flickDeceleration() const; + void setFlickDeceleration(qreal deceleration); + + void handleTouchEvent(QTouchEvent *event); + void handleWheelEvent(QWheelEvent *event); + void handleMousePressEvent(QMouseEvent *event); + void handleMouseMoveEvent(QMouseEvent *event); + void handleMouseReleaseEvent(QMouseEvent *event); + void handleMouseUngrabEvent(); + void handleTouchUngrabEvent(); + + void setMinimumZoomLevel(qreal min); + qreal minimumZoomLevel() const; + + void setMaximumZoomLevel(qreal max); + qreal maximumZoomLevel() const; + + void setMap(QPointer<QGeoMap> map); + + bool preventStealing() const; + void setPreventStealing(bool prevent); + +Q_SIGNALS: + void panActiveChanged(); + void pinchActiveChanged(); + void rotationActiveChanged(); + void tiltActiveChanged(); + void enabledChanged(); + void maximumZoomLevelChangeChanged(); + void acceptedGesturesChanged(); + void flickDecelerationChanged(); + void pinchStarted(QGeoMapPinchEvent *pinch); + void pinchUpdated(QGeoMapPinchEvent *pinch); + void pinchFinished(QGeoMapPinchEvent *pinch); + void panStarted(); + void panFinished(); + void flickStarted(); + void flickFinished(); + void rotationStarted(QGeoMapPinchEvent *pinch); + void rotationUpdated(QGeoMapPinchEvent *pinch); + void rotationFinished(QGeoMapPinchEvent *pinch); + void tiltStarted(QGeoMapPinchEvent *pinch); + void tiltUpdated(QGeoMapPinchEvent *pinch); + void tiltFinished(QGeoMapPinchEvent *pinch); + void preventStealingChanged(); +private: + void update(); + + // Create general data relating to the touch points + void touchPointStateMachine(); + void startOneTouchPoint(); + void updateOneTouchPoint(); + void startTwoTouchPoints(); + void updateTwoTouchPoints(); + + // All two fingers vertical parallel panning related code, which encompasses tilting + void tiltStateMachine(); + bool canStartTilt(); + void startTilt(); + void updateTilt(); + void endTilt(); + + // All two fingers rotation related code, which encompasses rotation + void rotationStateMachine(); + bool canStartRotation(); + void startRotation(); + void updateRotation(); + void endRotation(); + + // All pinch related code, which encompasses zoom + void pinchStateMachine(); + bool canStartPinch(); + void startPinch(); + void updatePinch(); + void endPinch(); + + // Pan related code (regardles of number of touch points), + // includes the flick based panning after letting go + void panStateMachine(); + bool canStartPan(); + void updatePan(); + bool tryStartFlick(); + void startFlick(int dx, int dy, int timeMs = 0); + void stopFlick(); + + bool pinchEnabled() const; + void setPinchEnabled(bool enabled); + bool rotationEnabled() const; + void setRotationEnabled(bool enabled); + bool tiltEnabled() const; + void setTiltEnabled(bool enabled); + bool panEnabled() const; + void setPanEnabled(bool enabled); + bool flickEnabled() const; + void setFlickEnabled(bool enabled); + +private Q_SLOTS: + void handleFlickAnimationStopped(); + + +private: + void stopPan(); + void clearTouchData(); + void updateFlickParameters(const QPointF &pos); + +private: + QPointer<QGeoMap> m_map; + QDeclarativeGeoMap *m_declarativeMap; + bool m_enabled; + + // This should be intended as a "two fingers gesture" struct + struct Pinch + { + Pinch() : m_pinchEnabled(true), m_rotationEnabled(true), m_tiltEnabled(true), + m_startDist(0), m_lastAngle(0.0) {} + + QGeoMapPinchEvent m_event; + bool m_pinchEnabled; + bool m_rotationEnabled; + bool m_tiltEnabled; + struct Zoom + { + Zoom() : m_minimum(0.0), m_maximum(30.0), m_start(0.0), m_previous(0.0), + maximumChange(4.0) {} + qreal m_minimum; + qreal m_maximum; + qreal m_start; + qreal m_previous; + qreal maximumChange; + } m_zoom; + + struct Rotation + { + Rotation() : m_startBearing(0.0), m_previousTouchAngle(0.0), m_totalAngle(0.0) {} + qreal m_startBearing; + qreal m_previousTouchAngle; // needed for detecting crossing +- 180 in a safer way + qreal m_totalAngle; + } m_rotation; + + struct Tilt + { + Tilt() {} + QPointF m_startTouchCentroid; + qreal m_startTilt; + } m_tilt; + + QPointF m_lastPoint1; + QPointF m_lastPoint2; + qreal m_startDist; + qreal m_lastAngle; + } m_pinch; + + AcceptedGestures m_acceptedGestures; + + struct Pan + { + Pan() : m_maxVelocity(2500), m_deceleration(2500), m_animation(0), m_flickEnabled(true), m_panEnabled(true) {} + + qreal m_maxVelocity; + qreal m_deceleration; + QQuickGeoCoordinateAnimation *m_animation; + bool m_flickEnabled; + bool m_panEnabled; + } m_flick; + + + // these are calculated regardless of gesture or number of touch points + QVector2D m_flickVector; + QElapsedTimer m_lastPosTime; + QPointF m_lastPos; + QVector<QTouchEvent::TouchPoint> m_allPoints; + QVector<QTouchEvent::TouchPoint> m_touchPoints; + QScopedPointer<QTouchEvent::TouchPoint> m_mousePoint; + QPointF m_sceneStartPoint1; + + // only set when two points in contact + QPointF m_sceneStartPoint2; + QGeoCoordinate m_startCoord; + QGeoCoordinate m_touchCenterCoord; + qreal m_twoTouchAngle; + qreal m_twoTouchAngleStart; + qreal m_distanceBetweenTouchPoints; + qreal m_distanceBetweenTouchPointsStart; + QPointF m_twoTouchPointsCentroidStart; + QPointF m_touchPointsCentroid; + bool m_preventStealing; + bool m_panEnabled; + +private: + // prototype state machine... + enum TouchPointState + { + touchPoints0, + touchPoints1, + touchPoints2 + } m_touchPointState; + + enum PinchState + { + pinchInactive, + pinchInactiveTwoPoints, + pinchActive + } m_pinchState; + + enum RotationState + { + rotationInactive, + rotationInactiveTwoPoints, + rotationActive + } m_rotationState; + + enum TiltState + { + tiltInactive, + tiltInactiveTwoPoints, + tiltActive + } m_tiltState; + + enum FlickState + { + flickInactive, + panActive, + flickActive + } m_flickState; + + inline void setTouchPointState(const TouchPointState state); + inline void setFlickState(const FlickState state); + inline void setTiltState(const TiltState state); + inline void setRotationState(const RotationState state); + inline void setPinchState(const PinchState state); +}; + +QT_END_NAMESPACE +QML_DECLARE_TYPE(QQuickGeoMapGestureArea) + +#endif // QQUICKGEOMAPGESTUREAREA_P_H |