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 
49 QT_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 
QDeclarativeGeoMapItemView(QQuickItem * parent)94 QDeclarativeGeoMapItemView::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 
~QDeclarativeGeoMapItemView()107 QDeclarativeGeoMapItemView::~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 */
componentComplete()116 void 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 
classBegin()129 void 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(m_delegateModel, &QQmlInstanceModel::modelUpdated, this, &QDeclarativeGeoMapItemView::modelUpdated);
137     connect(m_delegateModel, &QQmlInstanceModel::createdItem, this, &QDeclarativeGeoMapItemView::createdItem);
138 //    connect(m_delegateModel, &QQmlInstanceModel::destroyingItem, this, &QDeclarativeGeoMapItemView::destroyingItem);
139 //    connect(m_delegateModel, &QQmlInstanceModel::initItem, this, &QDeclarativeGeoMapItemView::initItem);
140 }
141 
destroyingItem(QObject *)142 void QDeclarativeGeoMapItemView::destroyingItem(QObject * /*object*/)
143 {
144 
145 }
146 
initItem(int,QObject *)147 void QDeclarativeGeoMapItemView::initItem(int /*index*/, QObject * /*object*/)
148 {
149 
150 }
151 
createdItem(int index,QObject *)152 void 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 *>(m_delegateModel->object(index, m_incubationMode));
170     if (item)
171         addDelegateToMap(item, index, true);
172     else
173         qWarning() << "QQmlDelegateModel:: object called in createdItem for " << index << " produced a null item";
174 }
175 
modelUpdated(const QQmlChangeSet & changeSet,bool reset)176 void 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(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(rit->second);
195             for (int idx = c.end() - 1; idx >= c.start(); --idx)
196                 removeDelegateFromMap(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(idx, m_incubationMode);
204             addDelegateToMap(qobject_cast<QQuickItem *>(delegateInstance), 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 */
model() const217 QVariant QDeclarativeGeoMapItemView::model() const
218 {
219     return m_itemModel;
220 }
221 
setModel(const QVariant & model)222 void 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 */
delegate() const241 QQmlComponent *QDeclarativeGeoMapItemView::delegate() const
242 {
243     return m_delegate;
244 }
245 
setDelegate(QQmlComponent * delegate)246 void 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 */
autoFitViewport() const266 bool QDeclarativeGeoMapItemView::autoFitViewport() const
267 {
268     return m_fitViewport;
269 }
270 
setAutoFitViewport(const bool & fit)271 void 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 */
fitViewport()283 void 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 */
setMap(QDeclarativeGeoMap * map)296 void 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 */
removeInstantiatedItems(bool transition)307 void 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(i, transition);
316 }
317 
318 /*!
319     \internal
320 
321     Instantiates all items.
322 */
instantiateAllItems()323 void 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(i, m_incubationMode);
333         addDelegateToMap(qobject_cast<QQuickItem *>(delegateInstance), i);
334     }
335 
336     fitViewport();
337 }
338 
setIncubateDelegates(bool useIncubators)339 void 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 
incubateDelegates() const349 bool QDeclarativeGeoMapItemView::incubateDelegates() const
350 {
351     return m_incubationMode == QQmlIncubator::Asynchronous;
352 }
353 
mapItems()354 QList<QQuickItem *> QDeclarativeGeoMapItemView::mapItems()
355 {
356     return m_instantiatedItems;
357 }
358 
disposeDelegate(QQuickItem * item)359 QQmlInstanceModel::ReleaseFlags QDeclarativeGeoMapItemView::disposeDelegate(QQuickItem *item)
360 {
361     disconnect(item, 0, this, 0);
362     removeDelegateFromMap(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(item);
366     return releaseStatus;
367 }
368 
removeDelegateFromMap(int index,bool transition)369 void QDeclarativeGeoMapItemView::removeDelegateFromMap(int index, bool transition)
370 {
371     if (index >= 0 && index < m_instantiatedItems.size()) {
372         QQuickItem *item = m_instantiatedItems.takeAt(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(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(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 
removeDelegateFromMap(QQuickItem * o)401 void QDeclarativeGeoMapItemView::removeDelegateFromMap(QQuickItem *o)
402 {
403     if (!m_map)
404         return;
405 
406     QDeclarativeGeoMapItemBase *item = qobject_cast<QDeclarativeGeoMapItemBase *>(o);
407     if (item) {
408         m_map->removeMapItem(item);
409         return;
410     }
411     QDeclarativeGeoMapItemView *view = qobject_cast<QDeclarativeGeoMapItemView *>(o);
412     if (view) {
413         m_map->removeMapItemView(view);
414         return;
415     }
416     QDeclarativeGeoMapItemGroup *group = qobject_cast<QDeclarativeGeoMapItemGroup *>(o);
417     if (group) {
418         m_map->removeMapItemGroup(group);
419         return;
420     }
421 }
422 
transitionItemOut(QQuickItem * o)423 void QDeclarativeGeoMapItemView::transitionItemOut(QQuickItem *o)
424 {
425     QDeclarativeGeoMapItemGroup *group = qobject_cast<QDeclarativeGeoMapItemGroup *>(o);
426     if (group) {
427         if (!group->m_transitionManager) {
428             QScopedPointer<QDeclarativeGeoMapItemTransitionManager>manager(new QDeclarativeGeoMapItemTransitionManager(group));
429             group->m_transitionManager.swap(manager);
430             group->m_transitionManager->m_view = this;
431         }
432         connect(group, SIGNAL(removeTransitionFinished()),
433                 this, SLOT(exitTransitionFinished()));
434 
435         group->m_transitionManager->transitionExit();
436         return;
437     }
438     QDeclarativeGeoMapItemBase *item = qobject_cast<QDeclarativeGeoMapItemBase *>(o);
439     if (item) {
440         if (!item->m_transitionManager) {
441             QScopedPointer<QDeclarativeGeoMapItemTransitionManager> manager(new QDeclarativeGeoMapItemTransitionManager(item));
442             item->m_transitionManager.swap(manager);
443             item->m_transitionManager->m_view = this;
444         }
445         connect(item, SIGNAL(removeTransitionFinished()),
446                 this, SLOT(exitTransitionFinished()) );
447 
448         item->m_transitionManager->transitionExit();
449         return;
450     }
451 }
452 
terminateExitTransition(QQuickItem * o)453 void QDeclarativeGeoMapItemView::terminateExitTransition(QQuickItem *o)
454 {
455     QDeclarativeGeoMapItemGroup *group = qobject_cast<QDeclarativeGeoMapItemGroup *>(o);
456     if (group && group->m_transitionManager) {
457         group->m_transitionManager->cancel();
458         return;
459     }
460     QDeclarativeGeoMapItemBase *item = qobject_cast<QDeclarativeGeoMapItemBase *>(o);
461     if (item && item->m_transitionManager) {
462         item->m_transitionManager->cancel();
463         return;
464     }
465 }
466 
exitTransitionFinished()467 void QDeclarativeGeoMapItemView::exitTransitionFinished()
468 {
469     QQuickItem *item = qobject_cast<QQuickItem *>(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 
addItemToMap(QDeclarativeGeoMapItemBase * item,int index,bool createdItem)481 void 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, 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(manager);
495             }
496             item->m_transitionManager->m_view = this;
497             item->m_transitionManager->transitionEnter();
498         }
499     }
500 }
501 
insertInstantiatedItem(int index,QQuickItem * o,bool createdItem)502 void QDeclarativeGeoMapItemView::insertInstantiatedItem(int index, QQuickItem *o, bool createdItem)
503 {
504     if (createdItem)
505         m_instantiatedItems.replace(index, o);
506     else
507         m_instantiatedItems.insert(index, o);
508 }
509 
addItemViewToMap(QDeclarativeGeoMapItemView * item,int index,bool createdItem)510 void 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, item, createdItem);
517         item->setParentItem(this);
518         m_map->addMapItemView(item);
519         if (m_enter) {
520             if (!item->m_transitionManager) {
521                 QScopedPointer<QDeclarativeGeoMapItemTransitionManager> manager(new QDeclarativeGeoMapItemTransitionManager(item));
522                 item->m_transitionManager.swap(manager);
523             }
524             item->m_transitionManager->m_view = this;
525             item->m_transitionManager->transitionEnter();
526         }
527     }
528 }
529 
addItemGroupToMap(QDeclarativeGeoMapItemGroup * item,int index,bool createdItem)530 void 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, item, createdItem);
537         item->setParentItem(this);
538         m_map->addMapItemGroup(item);
539         if (m_enter) {
540             if (!item->m_transitionManager) {
541                 QScopedPointer<QDeclarativeGeoMapItemTransitionManager>manager(new QDeclarativeGeoMapItemTransitionManager(item));
542                 item->m_transitionManager.swap(manager);
543             }
544             item->m_transitionManager->m_view = this;
545             item->m_transitionManager->transitionEnter();
546         }
547     }
548 }
549 
addDelegateToMap(QQuickItem * object,int index,bool createdItem)550 void QDeclarativeGeoMapItemView::addDelegateToMap(QQuickItem *object, int index, bool createdItem)
551 {
552     if (!object) {
553         if (!createdItem)
554             m_instantiatedItems.insert(index, 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(view, index, createdItem);
565         return;
566     }
567     QDeclarativeGeoMapItemGroup *group = qobject_cast<QDeclarativeGeoMapItemGroup *>(object);
568     if (group) {
569         addItemGroupToMap(group, index, createdItem);
570         return;
571     }
572     qWarning() << "addDelegateToMap called with a "<< object->metaObject()->className();
573 }
574 
575 QT_END_NAMESPACE
576 
577 
578