blob: 43e24620dde38a361cced8053fd20ef7b7cb1170 [file] [log] [blame]
/****************************************************************************
**
** 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 "qdeclarativegeomap_p.h"
#include "qdeclarativegeomapitembase_p.h"
#include <QtCore/QAbstractItemModel>
#include <QtQml/QQmlContext>
#include <QtQml/private/qqmlopenmetaobject_p.h>
#include <QtQuick/private/qquickanimation_p.h>
#include <QtQml/QQmlListProperty>
QT_BEGIN_NAMESPACE
/*!
\qmltype MapItemView
\instantiates QDeclarativeGeoMapItemView
\inqmlmodule QtLocation
\ingroup qml-QtLocation5-maps
\since QtLocation 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
*/
/*!
\qmlproperty Transition QtLocation::MapItemView::add
This property holds the transition that is applied to the map items created by the view
when they are instantiated and added to the map.
\since QtLocation 5.12
*/
/*!
\qmlproperty Transition QtLocation::MapItemView::remove
This property holds the transition that is applied to the map items created by the view
when they are removed.
\since QtLocation 5.12
*/
QDeclarativeGeoMapItemView::QDeclarativeGeoMapItemView(QQuickItem *parent)
: QDeclarativeGeoMapItemGroup(parent), m_componentCompleted(false), m_delegate(0),
m_map(0), m_fitViewport(false), m_delegateModel(0)
{
m_exit = new QQuickTransition(this);
QQmlListProperty<QQuickAbstractAnimation> anims = m_exit->animations();
QQuickNumberAnimation *ani = new QQuickNumberAnimation(m_exit);
ani->setProperty(QStringLiteral("opacity"));
ani->setTo(0.0);
ani->setDuration(300.0);
anims.append(&anims, ani);
}
QDeclarativeGeoMapItemView::~QDeclarativeGeoMapItemView()
{
// No need to remove instantiated items: if the MIV has instantiated items because it has been added
// to a Map (or is child of a Map), the Map destructor takes care of removing it and the instantiated items.
}
/*!
\internal
*/
void QDeclarativeGeoMapItemView::componentComplete()
{
QDeclarativeGeoMapItemGroup::componentComplete();
m_componentCompleted = true;
if (!m_itemModel.isNull())
m_delegateModel->setModel(m_itemModel);
if (m_delegate)
m_delegateModel->setDelegate(m_delegate);
m_delegateModel->componentComplete();
}
void QDeclarativeGeoMapItemView::classBegin()
{
QDeclarativeGeoMapItemGroup::classBegin();
QQmlContext *ctx = qmlContext(this);
m_delegateModel = new QQmlDelegateModel(ctx, this);
m_delegateModel->classBegin();
connect(m_delegateModel, &QQmlInstanceModel::modelUpdated, this, &QDeclarativeGeoMapItemView::modelUpdated);
connect(m_delegateModel, &QQmlInstanceModel::createdItem, this, &QDeclarativeGeoMapItemView::createdItem);
// connect(m_delegateModel, &QQmlInstanceModel::destroyingItem, this, &QDeclarativeGeoMapItemView::destroyingItem);
// connect(m_delegateModel, &QQmlInstanceModel::initItem, this, &QDeclarativeGeoMapItemView::initItem);
}
void QDeclarativeGeoMapItemView::destroyingItem(QObject * /*object*/)
{
}
void QDeclarativeGeoMapItemView::initItem(int /*index*/, QObject * /*object*/)
{
}
void QDeclarativeGeoMapItemView::createdItem(int index, QObject * /*object*/)
{
if (!m_map)
return;
// createdItem is emitted on asynchronous creation. In which case, object has to be invoked again.
// See QQmlDelegateModel::object for further info.
// DelegateModel apparently triggers this method in any case, that is:
// 1. Synchronous incubation, delegate instantiated on the first object() call (during the object() call!)
// 2. Async incubation, delegate not instantiated on the first object() call
// 3. Async incubation, delegate present in the cache, and returned on the first object() call.
// createdItem also called during the object() call.
if (m_creatingObject) {
// Falling into case 1. or 3. Returning early to prevent double referencing the delegate instance.
return;
}
QQuickItem *item = qobject_cast<QQuickItem *>(m_delegateModel->object(index, m_incubationMode));
if (item)
addDelegateToMap(item, index, true);
else
qWarning() << "QQmlDelegateModel:: object called in createdItem for " << index << " produced a null item";
}
void QDeclarativeGeoMapItemView::modelUpdated(const QQmlChangeSet &changeSet, bool reset)
{
if (!m_map) // everything will be done in instantiateAllItems. Removal is done by declarativegeomap.
return;
// move changes are expressed as one remove + one insert, with the same moveId.
// For simplicity, they will be treated as remove + insert.
// Changes will be also ignored, as they represent only data changes, not layout changes
if (reset) { // Assuming this means "remove everything already instantiated"
removeInstantiatedItems();
} else {
// Remove items from the back to the front to retain the mapping to what is received from the changesets
const QVector<QQmlChangeSet::Change> &removes = changeSet.removes();
std::map<int, int> mapRemoves;
for (int i = 0; i < removes.size(); i++)
mapRemoves.insert(std::pair<int, int>(removes.at(i).start(), i));
for (auto rit = mapRemoves.rbegin(); rit != mapRemoves.rend(); ++rit) {
const QQmlChangeSet::Change &c = removes.at(rit->second);
for (int idx = c.end() - 1; idx >= c.start(); --idx)
removeDelegateFromMap(idx);
}
}
QBoolBlocker createBlocker(m_creatingObject, true);
for (const QQmlChangeSet::Change &c: changeSet.inserts()) {
for (int idx = c.start(); idx < c.end(); idx++) {
QObject *delegateInstance = m_delegateModel->object(idx, m_incubationMode);
addDelegateToMap(qobject_cast<QQuickItem *>(delegateInstance), idx);
}
}
fitViewport();
}
/*!
\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 m_itemModel;
}
void QDeclarativeGeoMapItemView::setModel(const QVariant &model)
{
if (model == m_itemModel)
return;
m_itemModel = model;
if (m_componentCompleted)
m_delegateModel->setModel(m_itemModel);
emit modelChanged();
}
/*!
\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 m_delegate;
}
void QDeclarativeGeoMapItemView::setDelegate(QQmlComponent *delegate)
{
if (m_delegate == delegate)
return;
m_delegate = delegate;
if (m_componentCompleted)
m_delegateModel->setDelegate(m_delegate);
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 m_fitViewport;
}
void QDeclarativeGeoMapItemView::setAutoFitViewport(const bool &fit)
{
if (fit == m_fitViewport)
return;
m_fitViewport = fit;
fitViewport();
emit autoFitViewportChanged();
}
/*!
\internal
*/
void QDeclarativeGeoMapItemView::fitViewport()
{
if (!m_map || !m_map->mapReady() || !m_fitViewport)
return;
if (m_map->mapItems().size() > 0)
m_map->fitViewportToMapItems();
}
/*!
\internal
*/
void QDeclarativeGeoMapItemView::setMap(QDeclarativeGeoMap *map)
{
if (!map || m_map) // changing map on the fly not supported
return;
m_map = map;
instantiateAllItems();
}
/*!
\internal
*/
void QDeclarativeGeoMapItemView::removeInstantiatedItems(bool transition)
{
if (!m_map)
return;
// with transition = false removeInstantiatedItems aborts ongoing exit transitions //QTBUG-69195
// Backward as removeItemFromMap modifies m_instantiatedItems
for (int i = m_instantiatedItems.size() -1; i >= 0 ; i--)
removeDelegateFromMap(i, transition);
}
/*!
\internal
Instantiates all items.
*/
void QDeclarativeGeoMapItemView::instantiateAllItems()
{
// The assumption is that if m_instantiatedItems isn't empty, instantiated items have been already added
if (!m_componentCompleted || !m_map || !m_delegate || m_itemModel.isNull() || !m_instantiatedItems.isEmpty())
return;
// If here, m_delegateModel may contain data, but QQmlInstanceModel::object for each row hasn't been called yet.
QBoolBlocker createBlocker(m_creatingObject, true);
for (int i = 0; i < m_delegateModel->count(); i++) {
QObject *delegateInstance = m_delegateModel->object(i, m_incubationMode);
addDelegateToMap(qobject_cast<QQuickItem *>(delegateInstance), i);
}
fitViewport();
}
void QDeclarativeGeoMapItemView::setIncubateDelegates(bool useIncubators)
{
const QQmlIncubator::IncubationMode incubationMode =
(useIncubators) ? QQmlIncubator::Asynchronous : QQmlIncubator::Synchronous;
if (m_incubationMode == incubationMode)
return;
m_incubationMode = incubationMode;
emit incubateDelegatesChanged();
}
bool QDeclarativeGeoMapItemView::incubateDelegates() const
{
return m_incubationMode == QQmlIncubator::Asynchronous;
}
QList<QQuickItem *> QDeclarativeGeoMapItemView::mapItems()
{
return m_instantiatedItems;
}
QQmlInstanceModel::ReleaseFlags QDeclarativeGeoMapItemView::disposeDelegate(QQuickItem *item)
{
disconnect(item, 0, this, 0);
removeDelegateFromMap(item);
item->setParentItem(nullptr); // Needed because
item->setParent(nullptr); // m_delegateModel->release(item) does not destroy the item most of the times!!
QQmlInstanceModel::ReleaseFlags releaseStatus = m_delegateModel->release(item);
return releaseStatus;
}
void QDeclarativeGeoMapItemView::removeDelegateFromMap(int index, bool transition)
{
if (index >= 0 && index < m_instantiatedItems.size()) {
QQuickItem *item = m_instantiatedItems.takeAt(index);
if (!item) { // not yet incubated
// Don't cancel incubation explicitly when model rows are removed, as DelegateModel
// apparently takes care of incubating elements when the model remove those indices.
// Cancel them explicitly only when a MIV is removed from a map.
if (!transition)
m_delegateModel->cancel(index);
return;
}
// item can be either a QDeclarativeGeoMapItemBase or a QDeclarativeGeoMapItemGroup (subclass)
if (m_exit && m_map && transition) {
transitionItemOut(item);
} else {
if (m_exit && m_map && !transition) {
// check if the exit transition is still running, if so stop it.
// This can happen when explicitly calling Map.removeMapItemView, soon after adding it.
terminateExitTransition(item);
}
QQmlInstanceModel::ReleaseFlags releaseStatus = disposeDelegate(item);
#ifdef QT_DEBUG
if (releaseStatus == QQmlInstanceModel::Referenced)
qWarning() << "item "<< index << "(" << item << ") still referenced";
#else
Q_UNUSED(releaseStatus);
#endif
}
}
}
void QDeclarativeGeoMapItemView::removeDelegateFromMap(QQuickItem *o)
{
if (!m_map)
return;
QDeclarativeGeoMapItemBase *item = qobject_cast<QDeclarativeGeoMapItemBase *>(o);
if (item) {
m_map->removeMapItem(item);
return;
}
QDeclarativeGeoMapItemView *view = qobject_cast<QDeclarativeGeoMapItemView *>(o);
if (view) {
m_map->removeMapItemView(view);
return;
}
QDeclarativeGeoMapItemGroup *group = qobject_cast<QDeclarativeGeoMapItemGroup *>(o);
if (group) {
m_map->removeMapItemGroup(group);
return;
}
}
void QDeclarativeGeoMapItemView::transitionItemOut(QQuickItem *o)
{
QDeclarativeGeoMapItemGroup *group = qobject_cast<QDeclarativeGeoMapItemGroup *>(o);
if (group) {
if (!group->m_transitionManager) {
QScopedPointer<QDeclarativeGeoMapItemTransitionManager>manager(new QDeclarativeGeoMapItemTransitionManager(group));
group->m_transitionManager.swap(manager);
group->m_transitionManager->m_view = this;
}
connect(group, SIGNAL(removeTransitionFinished()),
this, SLOT(exitTransitionFinished()));
group->m_transitionManager->transitionExit();
return;
}
QDeclarativeGeoMapItemBase *item = qobject_cast<QDeclarativeGeoMapItemBase *>(o);
if (item) {
if (!item->m_transitionManager) {
QScopedPointer<QDeclarativeGeoMapItemTransitionManager> manager(new QDeclarativeGeoMapItemTransitionManager(item));
item->m_transitionManager.swap(manager);
item->m_transitionManager->m_view = this;
}
connect(item, SIGNAL(removeTransitionFinished()),
this, SLOT(exitTransitionFinished()) );
item->m_transitionManager->transitionExit();
return;
}
}
void QDeclarativeGeoMapItemView::terminateExitTransition(QQuickItem *o)
{
QDeclarativeGeoMapItemGroup *group = qobject_cast<QDeclarativeGeoMapItemGroup *>(o);
if (group && group->m_transitionManager) {
group->m_transitionManager->cancel();
return;
}
QDeclarativeGeoMapItemBase *item = qobject_cast<QDeclarativeGeoMapItemBase *>(o);
if (item && item->m_transitionManager) {
item->m_transitionManager->cancel();
return;
}
}
void QDeclarativeGeoMapItemView::exitTransitionFinished()
{
QQuickItem *item = qobject_cast<QQuickItem *>(sender());
if (!item)
return;
QQmlInstanceModel::ReleaseFlags releaseStatus = disposeDelegate(item);
#ifdef QT_DEBUG
if (releaseStatus == QQmlInstanceModel::Referenced)
qWarning() << "item "<<item<<" still referenced";
#else
Q_UNUSED(releaseStatus);
#endif
}
void QDeclarativeGeoMapItemView::addItemToMap(QDeclarativeGeoMapItemBase *item, int index, bool createdItem)
{
if (m_map && item->quickMap() == m_map) // test for *item done in the caller
return;
if (m_map) {
insertInstantiatedItem(index, item, createdItem);
item->setParentItem(this);
m_map->addMapItem(item);
if (m_enter) {
if (!item->m_transitionManager) {
QScopedPointer<QDeclarativeGeoMapItemTransitionManager>manager(new QDeclarativeGeoMapItemTransitionManager(item));
item->m_transitionManager.swap(manager);
}
item->m_transitionManager->m_view = this;
item->m_transitionManager->transitionEnter();
}
}
}
void QDeclarativeGeoMapItemView::insertInstantiatedItem(int index, QQuickItem *o, bool createdItem)
{
if (createdItem)
m_instantiatedItems.replace(index, o);
else
m_instantiatedItems.insert(index, o);
}
void QDeclarativeGeoMapItemView::addItemViewToMap(QDeclarativeGeoMapItemView *item, int index, bool createdItem)
{
if (m_map && item->quickMap() == m_map) // test for *item done in the caller
return;
if (m_map) {
insertInstantiatedItem(index, item, createdItem);
item->setParentItem(this);
m_map->addMapItemView(item);
if (m_enter) {
if (!item->m_transitionManager) {
QScopedPointer<QDeclarativeGeoMapItemTransitionManager> manager(new QDeclarativeGeoMapItemTransitionManager(item));
item->m_transitionManager.swap(manager);
}
item->m_transitionManager->m_view = this;
item->m_transitionManager->transitionEnter();
}
}
}
void QDeclarativeGeoMapItemView::addItemGroupToMap(QDeclarativeGeoMapItemGroup *item, int index, bool createdItem)
{
if (m_map && item->quickMap() == m_map) // test for *item done in the caller
return;
if (m_map) {
insertInstantiatedItem(index, item, createdItem);
item->setParentItem(this);
m_map->addMapItemGroup(item);
if (m_enter) {
if (!item->m_transitionManager) {
QScopedPointer<QDeclarativeGeoMapItemTransitionManager>manager(new QDeclarativeGeoMapItemTransitionManager(item));
item->m_transitionManager.swap(manager);
}
item->m_transitionManager->m_view = this;
item->m_transitionManager->transitionEnter();
}
}
}
void QDeclarativeGeoMapItemView::addDelegateToMap(QQuickItem *object, int index, bool createdItem)
{
if (!object) {
if (!createdItem)
m_instantiatedItems.insert(index, nullptr); // insert placeholder
return;
}
QDeclarativeGeoMapItemBase *item = qobject_cast<QDeclarativeGeoMapItemBase *>(object);
if (item) { // else createdItem will be emitted.
addItemToMap(item, index, createdItem);
return;
}
QDeclarativeGeoMapItemView *view = qobject_cast<QDeclarativeGeoMapItemView *>(object);
if (view) {
addItemViewToMap(view, index, createdItem);
return;
}
QDeclarativeGeoMapItemGroup *group = qobject_cast<QDeclarativeGeoMapItemGroup *>(object);
if (group) {
addItemGroupToMap(group, index, createdItem);
return;
}
qWarning() << "addDelegateToMap called with a "<< object->metaObject()->className();
}
QT_END_NAMESPACE