Revision control

++ // Copyright (C) 2015 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ #include "qdeclarativegeocodemodel_p.h"
++ #include "error_messages_p.h"
++
++ #include <QtCore/QCoreApplication>
++ #include <QtQml/QQmlInfo>
++ #include <QtPositioning/QGeoCircle>
++ #include <QtLocation/QGeoServiceProvider>
++ #include <QtLocation/QGeoCodingManager>
++ #include <QtLocation/private/qgeocodereply_p.h>
++ #include <QtPositioning/QGeoPolygon>
++
++ QT_BEGIN_NAMESPACE
++
++ /*!
++ \qmltype GeocodeModel
++ \nativetype QDeclarativeGeocodeModel
++ \inqmlmodule QtLocation
++ \ingroup qml-QtLocation5-geocoding
++ \since QtLocation 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 [QML] {Location}
++ objects, as a list with the role name "locationData". See the documentation
++ for \l [QML] {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)
++ {
++ }
++
++ 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();
++ }
++ if (reply_) {
++ 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_, &QDeclarativeGeoServiceProvider::attached,
++ this, &QDeclarativeGeocodeModel::pluginReady);
++ }
++ }
++
++ /*!
++ \internal
++ */
++ void QDeclarativeGeocodeModel::pluginReady()
++ {
++ QGeoServiceProvider *serviceProvider = plugin_->sharedGeoServiceProvider();
++ QGeoCodingManager *geocodingManager = serviceProvider->geocodingManager();
++
++ if (serviceProvider->geocodingError() != QGeoServiceProvider::NoError) {
++ QDeclarativeGeocodeModel::GeocodeError newError = UnknownError;
++ switch (serviceProvider->geocodingError()) {
++ 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->geocodingErrorString());
++ return;
++ }
++
++ if (!geocodingManager) {
++ setError(EngineNotSetError,tr("Plugin does not support (reverse) geocoding."));
++ return;
++ }
++
++ connect(geocodingManager, &QGeoCodingManager::finished,
++ this, &QDeclarativeGeocodeModel::geocodeFinished);
++ connect(geocodingManager, &QGeoCodingManager::errorOccurred,
++ this, &QDeclarativeGeocodeModel::geocodeError);
++
++ if (complete_ && autoUpdate_)
++ update();
++ }
++
++ /*!
++ \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 if (boundingArea_.type() == QGeoShape::PolygonType)
++ return QVariant::fromValue(QGeoPolygon(boundingArea_));
++ else
++ return QVariant::fromValue(boundingArea_);
++ }
++
++ void QDeclarativeGeocodeModel::geocodeFinished(QGeoCodeReply *reply)
++ {
++ if (reply != reply_ || reply->error() != QGeoCodeReply::NoError)
++ return;
++
++ reply->deleteLater();
++ reply_ = 0;
++ int oldCount = declarativeLocations_.count();
++ setLocations(reply->locations());
++ setError(NoError, QString());
++ setStatus(QDeclarativeGeocodeModel::Ready);
++ emit locationsChanged();
++ if (oldCount != declarativeLocations_.count())
++ emit countChanged();
++ }
++
++ /*!
++ \internal
++ */
++ void QDeclarativeGeocodeModel::geocodeError(QGeoCodeReply *reply,
++ QGeoCodeReply::Error error,
++ const QString &errorString)
++ {
++ if (reply != reply_)
++ return;
++
++ reply->deleteLater();
++ reply_ = 0;
++ 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);
++ }
++
++ /*!
++ \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 (const auto &location : locations)
++ declarativeLocations_.append(new QDeclarativeGeoLocation(location, this));
++ 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 index)
++
++ Returns the \l [QML] {Location} at given \a 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 nullptr;
++ }
++ 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 [QtPositioning]{geoCoordinate} - 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.typeId() == QMetaType::QString) {
++ 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_, &QDeclarativeGeoAddress::countryChanged,
++ this, &QDeclarativeGeocodeModel::queryContentChanged);
++ connect(address_, &QDeclarativeGeoAddress::countryCodeChanged,
++ this, &QDeclarativeGeocodeModel::queryContentChanged);
++ connect(address_, &QDeclarativeGeoAddress::stateChanged,
++ this, &QDeclarativeGeocodeModel::queryContentChanged);
++ connect(address_, &QDeclarativeGeoAddress::countyChanged,
++ this, &QDeclarativeGeocodeModel::queryContentChanged);
++ connect(address_, &QDeclarativeGeoAddress::cityChanged,
++ this, &QDeclarativeGeocodeModel::queryContentChanged);
++ connect(address_, &QDeclarativeGeoAddress::districtChanged,
++ this, &QDeclarativeGeocodeModel::queryContentChanged);
++ connect(address_, &QDeclarativeGeoAddress::streetChanged,
++ this, &QDeclarativeGeocodeModel::queryContentChanged);
++ connect(address_, &QDeclarativeGeoAddress::postalCodeChanged,
++ this, &QDeclarativeGeocodeModel::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 [QtPositioning]{geoCoordinate} 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