summaryrefslogtreecommitdiffstats
path: root/src/location/quickmapitems/qdeclarativegeomapitemview.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/location/quickmapitems/qdeclarativegeomapitemview.cpp')
-rw-r--r--src/location/quickmapitems/qdeclarativegeomapitemview.cpp578
1 files changed, 578 insertions, 0 deletions
diff --git a/src/location/quickmapitems/qdeclarativegeomapitemview.cpp b/src/location/quickmapitems/qdeclarativegeomapitemview.cpp
new file mode 100644
index 00000000..27527d35
--- /dev/null
+++ b/src/location/quickmapitems/qdeclarativegeomapitemview.cpp
@@ -0,0 +1,578 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 Jolla Ltd.
+** Contact: Aaron McCarthy <aaron.mccarthy@jollamobile.com>
+** Copyright (C) 2022 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtLocation module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qdeclarativegeomapitemview_p.h"
+#include "qdeclarativegeomap_p.h"
+#include "qdeclarativegeomapitembase_p.h"
+
+#include <QtCore/QAbstractItemModel>
+#include <QtQml/QQmlContext>
+#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_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 QList<QQmlChangeSet::Change> &removes = changeSet.removes();
+ std::map<int, int> mapRemoves;
+ for (qsizetype 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 (auto idx = c.end() - 1; idx >= c.start(); --idx)
+ removeDelegateFromMap(idx);
+ }
+ }
+
+ QBoolBlocker createBlocker(m_creatingObject, true);
+ for (const QQmlChangeSet::Change &c: changeSet.inserts()) {
+ for (auto 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 bool 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 (qsizetype 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 (qsizetype 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) {
+ std::unique_ptr<QDeclarativeGeoMapItemTransitionManager>manager(new QDeclarativeGeoMapItemTransitionManager(group));
+ group->m_transitionManager.swap(manager);
+ group->m_transitionManager->m_view = this;
+ }
+ connect(group, &QDeclarativeGeoMapItemGroup::removeTransitionFinished,
+ this, &QDeclarativeGeoMapItemView::exitTransitionFinished);
+
+ group->m_transitionManager->transitionExit();
+ return;
+ }
+ QDeclarativeGeoMapItemBase *item = qobject_cast<QDeclarativeGeoMapItemBase *>(o);
+ if (item) {
+ if (!item->m_transitionManager) {
+ std::unique_ptr<QDeclarativeGeoMapItemTransitionManager> manager(new QDeclarativeGeoMapItemTransitionManager(item));
+ item->m_transitionManager.swap(manager);
+ item->m_transitionManager->m_view = this;
+ }
+ connect(item, &QDeclarativeGeoMapItemBase::removeTransitionFinished,
+ this, &QDeclarativeGeoMapItemView::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) {
+ std::unique_ptr<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) {
+ std::unique_ptr<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) {
+ std::unique_ptr<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
+
+