1/****************************************************************************
2**
3** Copyright (C) 2015 Jolla Ltd.
4** Contact: Aaron McCarthy <aaron.mccarthy@jollamobile.com>
5** Copyright (C) 2015 The Qt Company Ltd.
6** Contact: http://www.qt.io/licensing/
7**
8** This file is part of the QtLocation module of the Qt Toolkit.
9**
10** $QT_BEGIN_LICENSE:LGPL3$
11** Commercial License Usage
12** Licensees holding valid commercial Qt licenses may use this file in
13** accordance with the commercial license agreement provided with the
14** Software or, alternatively, in accordance with the terms contained in
15** a written agreement between you and The Qt Company. For licensing terms
16** and conditions see http://www.qt.io/terms-conditions. For further
17** information use the contact form at http://www.qt.io/contact-us.
18**
19** GNU Lesser General Public License Usage
20** Alternatively, this file may be used under the terms of the GNU Lesser
21** General Public License version 3 as published by the Free Software
22** Foundation and appearing in the file LICENSE.LGPLv3 included in the
23** packaging of this file. Please review the following information to
24** ensure the GNU Lesser General Public License version 3 requirements
25** will be met: https://www.gnu.org/licenses/lgpl.html.
26**
27** GNU General Public License Usage
28** Alternatively, this file may be used under the terms of the GNU
29** General Public License version 2.0 or later as published by the Free
30** Software Foundation and appearing in the file LICENSE.GPL included in
31** the packaging of this file. Please review the following information to
32** ensure the GNU General Public License version 2.0 requirements will be
33** met: http://www.gnu.org/licenses/gpl-2.0.html.
34**
35** $QT_END_LICENSE$
36**
37****************************************************************************/
38
39#include "qdeclarativegeomapitemview_p.h"
40#include "qdeclarativegeomap_p.h"
41#include "qdeclarativegeomapitembase_p.h"
42
43#include <QtCore/QAbstractItemModel>
44#include <QtQml/QQmlContext>
45#include <QtQml/private/qqmlopenmetaobject_p.h>
46#include <QtQuick/private/qquickanimation_p.h>
47#include <QtQml/QQmlListProperty>
48
49QT_BEGIN_NAMESPACE
50
51/*!
52 \qmltype MapItemView
53 \instantiates QDeclarativeGeoMapItemView
54 \inqmlmodule QtLocation
55 \ingroup qml-QtLocation5-maps
56 \since QtLocation 5.5
57 \inherits QObject
58
59 \brief The MapItemView is used to populate Map from a model.
60
61 The MapItemView is used to populate Map with MapItems from a model.
62 The MapItemView type only makes sense when contained in a Map,
63 meaning that it has no standalone presentation.
64
65 \section2 Example Usage
66
67 This example demonstrates how to use the MapViewItem object to display
68 a \l{Route}{route} on a \l{Map}{map}:
69
70 \snippet declarative/maps.qml QtQuick import
71 \snippet declarative/maps.qml QtLocation import
72 \codeline
73 \snippet declarative/maps.qml MapRoute
74*/
75
76/*!
77 \qmlproperty Transition QtLocation::MapItemView::add
78
79 This property holds the transition that is applied to the map items created by the view
80 when they are instantiated and added to the map.
81
82 \since QtLocation 5.12
83*/
84
85/*!
86 \qmlproperty Transition QtLocation::MapItemView::remove
87
88 This property holds the transition that is applied to the map items created by the view
89 when they are removed.
90
91 \since QtLocation 5.12
92*/
93
94QDeclarativeGeoMapItemView::QDeclarativeGeoMapItemView(QQuickItem *parent)
95 : QDeclarativeGeoMapItemGroup(parent), m_componentCompleted(false), m_delegate(0),
96 m_map(0), m_fitViewport(false), m_delegateModel(0)
97{
98 m_exit = new QQuickTransition(this);
99 QQmlListProperty<QQuickAbstractAnimation> anims = m_exit->animations();
100 QQuickNumberAnimation *ani = new QQuickNumberAnimation(m_exit);
101 ani->setProperty(QStringLiteral("opacity"));
102 ani->setTo(0.0);
103 ani->setDuration(300.0);
104 anims.append(&anims, ani);
105}
106
107QDeclarativeGeoMapItemView::~QDeclarativeGeoMapItemView()
108{
109 // No need to remove instantiated items: if the MIV has instantiated items because it has been added
110 // to a Map (or is child of a Map), the Map destructor takes care of removing it and the instantiated items.
111}
112
113/*!
114 \internal
115*/
116void QDeclarativeGeoMapItemView::componentComplete()
117{
118 QDeclarativeGeoMapItemGroup::componentComplete();
119 m_componentCompleted = true;
120 if (!m_itemModel.isNull())
121 m_delegateModel->setModel(m_itemModel);
122
123 if (m_delegate)
124 m_delegateModel->setDelegate(m_delegate);
125
126 m_delegateModel->componentComplete();
127}
128
129void QDeclarativeGeoMapItemView::classBegin()
130{
131 QDeclarativeGeoMapItemGroup::classBegin();
132 QQmlContext *ctx = qmlContext(this);
133 m_delegateModel = new QQmlDelegateModel(ctx, this);
134 m_delegateModel->classBegin();
135
136 connect(sender: m_delegateModel, signal: &QQmlInstanceModel::modelUpdated, receiver: this, slot: &QDeclarativeGeoMapItemView::modelUpdated);
137 connect(sender: m_delegateModel, signal: &QQmlInstanceModel::createdItem, receiver: this, slot: &QDeclarativeGeoMapItemView::createdItem);
138// connect(m_delegateModel, &QQmlInstanceModel::destroyingItem, this, &QDeclarativeGeoMapItemView::destroyingItem);
139// connect(m_delegateModel, &QQmlInstanceModel::initItem, this, &QDeclarativeGeoMapItemView::initItem);
140}
141
142void QDeclarativeGeoMapItemView::destroyingItem(QObject * /*object*/)
143{
144
145}
146
147void QDeclarativeGeoMapItemView::initItem(int /*index*/, QObject * /*object*/)
148{
149
150}
151
152void QDeclarativeGeoMapItemView::createdItem(int index, QObject * /*object*/)
153{
154 if (!m_map)
155 return;
156 // createdItem is emitted on asynchronous creation. In which case, object has to be invoked again.
157 // See QQmlDelegateModel::object for further info.
158
159 // DelegateModel apparently triggers this method in any case, that is:
160 // 1. Synchronous incubation, delegate instantiated on the first object() call (during the object() call!)
161 // 2. Async incubation, delegate not instantiated on the first object() call
162 // 3. Async incubation, delegate present in the cache, and returned on the first object() call.
163 // createdItem also called during the object() call.
164 if (m_creatingObject) {
165 // Falling into case 1. or 3. Returning early to prevent double referencing the delegate instance.
166 return;
167 }
168
169 QQuickItem *item = qobject_cast<QQuickItem *>(object: m_delegateModel->object(index, incubationMode: m_incubationMode));
170 if (item)
171 addDelegateToMap(object: item, index, createdItem: true);
172 else
173 qWarning() << "QQmlDelegateModel:: object called in createdItem for " << index << " produced a null item";
174}
175
176void QDeclarativeGeoMapItemView::modelUpdated(const QQmlChangeSet &changeSet, bool reset)
177{
178 if (!m_map) // everything will be done in instantiateAllItems. Removal is done by declarativegeomap.
179 return;
180
181 // move changes are expressed as one remove + one insert, with the same moveId.
182 // For simplicity, they will be treated as remove + insert.
183 // Changes will be also ignored, as they represent only data changes, not layout changes
184 if (reset) { // Assuming this means "remove everything already instantiated"
185 removeInstantiatedItems();
186 } else {
187 // Remove items from the back to the front to retain the mapping to what is received from the changesets
188 const QVector<QQmlChangeSet::Change> &removes = changeSet.removes();
189 std::map<int, int> mapRemoves;
190 for (int i = 0; i < removes.size(); i++)
191 mapRemoves.insert(x: std::pair<int, int>(removes.at(i).start(), i));
192
193 for (auto rit = mapRemoves.rbegin(); rit != mapRemoves.rend(); ++rit) {
194 const QQmlChangeSet::Change &c = removes.at(i: rit->second);
195 for (int idx = c.end() - 1; idx >= c.start(); --idx)
196 removeDelegateFromMap(index: idx);
197 }
198 }
199
200 QBoolBlocker createBlocker(m_creatingObject, true);
201 for (const QQmlChangeSet::Change &c: changeSet.inserts()) {
202 for (int idx = c.start(); idx < c.end(); idx++) {
203 QObject *delegateInstance = m_delegateModel->object(index: idx, incubationMode: m_incubationMode);
204 addDelegateToMap(object: qobject_cast<QQuickItem *>(object: delegateInstance), index: idx);
205 }
206 }
207
208 fitViewport();
209}
210
211/*!
212 \qmlproperty model QtLocation::MapItemView::model
213
214 This property holds the model that provides data used for creating the map items defined by the
215 delegate. Only QAbstractItemModel based models are supported.
216*/
217QVariant QDeclarativeGeoMapItemView::model() const
218{
219 return m_itemModel;
220}
221
222void QDeclarativeGeoMapItemView::setModel(const QVariant &model)
223{
224 if (model == m_itemModel)
225 return;
226
227 m_itemModel = model;
228 if (m_componentCompleted)
229 m_delegateModel->setModel(m_itemModel);
230
231 emit modelChanged();
232}
233
234/*!
235 \qmlproperty Component QtLocation::MapItemView::delegate
236
237 This property holds the delegate which defines how each item in the
238 model should be displayed. The Component must contain exactly one
239 MapItem -derived object as the root object.
240*/
241QQmlComponent *QDeclarativeGeoMapItemView::delegate() const
242{
243 return m_delegate;
244}
245
246void QDeclarativeGeoMapItemView::setDelegate(QQmlComponent *delegate)
247{
248 if (m_delegate == delegate)
249 return;
250
251 m_delegate = delegate;
252 if (m_componentCompleted)
253 m_delegateModel->setDelegate(m_delegate);
254
255 emit delegateChanged();
256}
257
258/*!
259 \qmlproperty Component QtLocation::MapItemView::autoFitViewport
260
261 This property controls whether to automatically pan and zoom the viewport
262 to display all map items when items are added or removed.
263
264 Defaults to false.
265*/
266bool QDeclarativeGeoMapItemView::autoFitViewport() const
267{
268 return m_fitViewport;
269}
270
271void QDeclarativeGeoMapItemView::setAutoFitViewport(const bool &fit)
272{
273 if (fit == m_fitViewport)
274 return;
275 m_fitViewport = fit;
276 fitViewport();
277 emit autoFitViewportChanged();
278}
279
280/*!
281 \internal
282*/
283void QDeclarativeGeoMapItemView::fitViewport()
284{
285
286 if (!m_map || !m_map->mapReady() || !m_fitViewport)
287 return;
288
289 if (m_map->mapItems().size() > 0)
290 m_map->fitViewportToMapItems();
291}
292
293/*!
294 \internal
295*/
296void QDeclarativeGeoMapItemView::setMap(QDeclarativeGeoMap *map)
297{
298 if (!map || m_map) // changing map on the fly not supported
299 return;
300 m_map = map;
301 instantiateAllItems();
302}
303
304/*!
305 \internal
306*/
307void QDeclarativeGeoMapItemView::removeInstantiatedItems(bool transition)
308{
309 if (!m_map)
310 return;
311
312 // with transition = false removeInstantiatedItems aborts ongoing exit transitions //QTBUG-69195
313 // Backward as removeItemFromMap modifies m_instantiatedItems
314 for (int i = m_instantiatedItems.size() -1; i >= 0 ; i--)
315 removeDelegateFromMap(index: i, transition);
316}
317
318/*!
319 \internal
320
321 Instantiates all items.
322*/
323void QDeclarativeGeoMapItemView::instantiateAllItems()
324{
325 // The assumption is that if m_instantiatedItems isn't empty, instantiated items have been already added
326 if (!m_componentCompleted || !m_map || !m_delegate || m_itemModel.isNull() || !m_instantiatedItems.isEmpty())
327 return;
328
329 // If here, m_delegateModel may contain data, but QQmlInstanceModel::object for each row hasn't been called yet.
330 QBoolBlocker createBlocker(m_creatingObject, true);
331 for (int i = 0; i < m_delegateModel->count(); i++) {
332 QObject *delegateInstance = m_delegateModel->object(index: i, incubationMode: m_incubationMode);
333 addDelegateToMap(object: qobject_cast<QQuickItem *>(object: delegateInstance), index: i);
334 }
335
336 fitViewport();
337}
338
339void QDeclarativeGeoMapItemView::setIncubateDelegates(bool useIncubators)
340{
341 const QQmlIncubator::IncubationMode incubationMode =
342 (useIncubators) ? QQmlIncubator::Asynchronous : QQmlIncubator::Synchronous;
343 if (m_incubationMode == incubationMode)
344 return;
345 m_incubationMode = incubationMode;
346 emit incubateDelegatesChanged();
347}
348
349bool QDeclarativeGeoMapItemView::incubateDelegates() const
350{
351 return m_incubationMode == QQmlIncubator::Asynchronous;
352}
353
354QList<QQuickItem *> QDeclarativeGeoMapItemView::mapItems()
355{
356 return m_instantiatedItems;
357}
358
359QQmlInstanceModel::ReleaseFlags QDeclarativeGeoMapItemView::disposeDelegate(QQuickItem *item)
360{
361 disconnect(sender: item, signal: 0, receiver: this, member: 0);
362 removeDelegateFromMap(o: item);
363 item->setParentItem(nullptr); // Needed because
364 item->setParent(nullptr); // m_delegateModel->release(item) does not destroy the item most of the times!!
365 QQmlInstanceModel::ReleaseFlags releaseStatus = m_delegateModel->release(object: item);
366 return releaseStatus;
367}
368
369void QDeclarativeGeoMapItemView::removeDelegateFromMap(int index, bool transition)
370{
371 if (index >= 0 && index < m_instantiatedItems.size()) {
372 QQuickItem *item = m_instantiatedItems.takeAt(i: index);
373 if (!item) { // not yet incubated
374 // Don't cancel incubation explicitly when model rows are removed, as DelegateModel
375 // apparently takes care of incubating elements when the model remove those indices.
376 // Cancel them explicitly only when a MIV is removed from a map.
377 if (!transition)
378 m_delegateModel->cancel(index);
379 return;
380 }
381 // item can be either a QDeclarativeGeoMapItemBase or a QDeclarativeGeoMapItemGroup (subclass)
382 if (m_exit && m_map && transition) {
383 transitionItemOut(o: item);
384 } else {
385 if (m_exit && m_map && !transition) {
386 // check if the exit transition is still running, if so stop it.
387 // This can happen when explicitly calling Map.removeMapItemView, soon after adding it.
388 terminateExitTransition(o: item);
389 }
390 QQmlInstanceModel::ReleaseFlags releaseStatus = disposeDelegate(item);
391#ifdef QT_DEBUG
392 if (releaseStatus == QQmlInstanceModel::Referenced)
393 qWarning() << "item "<< index << "(" << item << ") still referenced";
394#else
395 Q_UNUSED(releaseStatus);
396#endif
397 }
398 }
399}
400
401void QDeclarativeGeoMapItemView::removeDelegateFromMap(QQuickItem *o)
402{
403 if (!m_map)
404 return;
405
406 QDeclarativeGeoMapItemBase *item = qobject_cast<QDeclarativeGeoMapItemBase *>(object: o);
407 if (item) {
408 m_map->removeMapItem(item);
409 return;
410 }
411 QDeclarativeGeoMapItemView *view = qobject_cast<QDeclarativeGeoMapItemView *>(object: o);
412 if (view) {
413 m_map->removeMapItemView(itemView: view);
414 return;
415 }
416 QDeclarativeGeoMapItemGroup *group = qobject_cast<QDeclarativeGeoMapItemGroup *>(object: o);
417 if (group) {
418 m_map->removeMapItemGroup(itemGroup: group);
419 return;
420 }
421}
422
423void QDeclarativeGeoMapItemView::transitionItemOut(QQuickItem *o)
424{
425 QDeclarativeGeoMapItemGroup *group = qobject_cast<QDeclarativeGeoMapItemGroup *>(object: o);
426 if (group) {
427 if (!group->m_transitionManager) {
428 QScopedPointer<QDeclarativeGeoMapItemTransitionManager>manager(new QDeclarativeGeoMapItemTransitionManager(group));
429 group->m_transitionManager.swap(other&: manager);
430 group->m_transitionManager->m_view = this;
431 }
432 connect(sender: group, SIGNAL(removeTransitionFinished()),
433 receiver: this, SLOT(exitTransitionFinished()));
434
435 group->m_transitionManager->transitionExit();
436 return;
437 }
438 QDeclarativeGeoMapItemBase *item = qobject_cast<QDeclarativeGeoMapItemBase *>(object: o);
439 if (item) {
440 if (!item->m_transitionManager) {
441 QScopedPointer<QDeclarativeGeoMapItemTransitionManager> manager(new QDeclarativeGeoMapItemTransitionManager(item));
442 item->m_transitionManager.swap(other&: manager);
443 item->m_transitionManager->m_view = this;
444 }
445 connect(sender: item, SIGNAL(removeTransitionFinished()),
446 receiver: this, SLOT(exitTransitionFinished()) );
447
448 item->m_transitionManager->transitionExit();
449 return;
450 }
451}
452
453void QDeclarativeGeoMapItemView::terminateExitTransition(QQuickItem *o)
454{
455 QDeclarativeGeoMapItemGroup *group = qobject_cast<QDeclarativeGeoMapItemGroup *>(object: o);
456 if (group && group->m_transitionManager) {
457 group->m_transitionManager->cancel();
458 return;
459 }
460 QDeclarativeGeoMapItemBase *item = qobject_cast<QDeclarativeGeoMapItemBase *>(object: o);
461 if (item && item->m_transitionManager) {
462 item->m_transitionManager->cancel();
463 return;
464 }
465}
466
467void QDeclarativeGeoMapItemView::exitTransitionFinished()
468{
469 QQuickItem *item = qobject_cast<QQuickItem *>(object: sender());
470 if (!item)
471 return;
472 QQmlInstanceModel::ReleaseFlags releaseStatus = disposeDelegate(item);
473#ifdef QT_DEBUG
474 if (releaseStatus == QQmlInstanceModel::Referenced)
475 qWarning() << "item "<<item<<" still referenced";
476#else
477 Q_UNUSED(releaseStatus);
478#endif
479}
480
481void QDeclarativeGeoMapItemView::addItemToMap(QDeclarativeGeoMapItemBase *item, int index, bool createdItem)
482{
483
484 if (m_map && item->quickMap() == m_map) // test for *item done in the caller
485 return;
486
487 if (m_map) {
488 insertInstantiatedItem(index, o: item, createdItem);
489 item->setParentItem(this);
490 m_map->addMapItem(item);
491 if (m_enter) {
492 if (!item->m_transitionManager) {
493 QScopedPointer<QDeclarativeGeoMapItemTransitionManager>manager(new QDeclarativeGeoMapItemTransitionManager(item));
494 item->m_transitionManager.swap(other&: manager);
495 }
496 item->m_transitionManager->m_view = this;
497 item->m_transitionManager->transitionEnter();
498 }
499 }
500}
501
502void QDeclarativeGeoMapItemView::insertInstantiatedItem(int index, QQuickItem *o, bool createdItem)
503{
504 if (createdItem)
505 m_instantiatedItems.replace(i: index, t: o);
506 else
507 m_instantiatedItems.insert(i: index, t: o);
508}
509
510void QDeclarativeGeoMapItemView::addItemViewToMap(QDeclarativeGeoMapItemView *item, int index, bool createdItem)
511{
512 if (m_map && item->quickMap() == m_map) // test for *item done in the caller
513 return;
514
515 if (m_map) {
516 insertInstantiatedItem(index, o: item, createdItem);
517 item->setParentItem(this);
518 m_map->addMapItemView(itemView: item);
519 if (m_enter) {
520 if (!item->m_transitionManager) {
521 QScopedPointer<QDeclarativeGeoMapItemTransitionManager> manager(new QDeclarativeGeoMapItemTransitionManager(item));
522 item->m_transitionManager.swap(other&: manager);
523 }
524 item->m_transitionManager->m_view = this;
525 item->m_transitionManager->transitionEnter();
526 }
527 }
528}
529
530void QDeclarativeGeoMapItemView::addItemGroupToMap(QDeclarativeGeoMapItemGroup *item, int index, bool createdItem)
531{
532 if (m_map && item->quickMap() == m_map) // test for *item done in the caller
533 return;
534
535 if (m_map) {
536 insertInstantiatedItem(index, o: item, createdItem);
537 item->setParentItem(this);
538 m_map->addMapItemGroup(itemGroup: item);
539 if (m_enter) {
540 if (!item->m_transitionManager) {
541 QScopedPointer<QDeclarativeGeoMapItemTransitionManager>manager(new QDeclarativeGeoMapItemTransitionManager(item));
542 item->m_transitionManager.swap(other&: manager);
543 }
544 item->m_transitionManager->m_view = this;
545 item->m_transitionManager->transitionEnter();
546 }
547 }
548}
549
550void QDeclarativeGeoMapItemView::addDelegateToMap(QQuickItem *object, int index, bool createdItem)
551{
552 if (!object) {
553 if (!createdItem)
554 m_instantiatedItems.insert(i: index, t: nullptr); // insert placeholder
555 return;
556 }
557 QDeclarativeGeoMapItemBase *item = qobject_cast<QDeclarativeGeoMapItemBase *>(object);
558 if (item) { // else createdItem will be emitted.
559 addItemToMap(item, index, createdItem);
560 return;
561 }
562 QDeclarativeGeoMapItemView *view = qobject_cast<QDeclarativeGeoMapItemView *>(object);
563 if (view) {
564 addItemViewToMap(item: view, index, createdItem);
565 return;
566 }
567 QDeclarativeGeoMapItemGroup *group = qobject_cast<QDeclarativeGeoMapItemGroup *>(object);
568 if (group) {
569 addItemGroupToMap(item: group, index, createdItem);
570 return;
571 }
572 qWarning() << "addDelegateToMap called with a "<< object->metaObject()->className();
573}
574
575QT_END_NAMESPACE
576
577
578

source code of qtlocation/src/location/declarativemaps/qdeclarativegeomapitemview.cpp