| /**************************************************************************** |
| ** |
| ** 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 |
| |
| |