1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the QtLocation module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL3$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or later as published by the Free
28 ** Software Foundation and appearing in the file LICENSE.GPL included in
29 ** the packaging of this file. Please review the following information to
30 ** ensure the GNU General Public License version 2.0 requirements will be
31 ** met: http://www.gnu.org/licenses/gpl-2.0.html.
32 **
33 ** $QT_END_LICENSE$
34 **
35 ****************************************************************************/
36 
37 #include "qmapobjectview_p.h"
38 #include "qmapobjectview_p_p.h"
39 #include <private/qqmldelegatemodel_p.h>
40 #include <QtLocation/private/qgeomap_p.h>
41 
42 QT_BEGIN_NAMESPACE
43 
44 /*!
45     \qmltype MapObjectView
46     \instantiates QMapObjectView
47     \inqmlmodule Qt.labs.location
48     \ingroup qml-QtLocation5-maps
49     \inherits QGeoMapObject
50 
51     \brief The MapObjectView is used to populate Map with map objects from a model.
52 
53     The MapObjectView is used to populate Map with map objects, either from a model or via
54     \l addMapObject or \l removeMapObject.
55 
56     The MapObjectView type only makes sense when contained in a Map, meaning that it will not work when added inside
57     other QML elements.
58     This can also be intended as an object layer on top of a Map.
59 */
60 
61 /*
62 
63     QMapObjectViewPrivate
64 
65 */
66 
67 static const QQmlIncubator::IncubationMode incubationMode = QQmlIncubator::Asynchronous;
68 
QMapObjectViewPrivate(QGeoMapObject * q)69 QMapObjectViewPrivate::QMapObjectViewPrivate(QGeoMapObject *q)
70     : QGeoMapObjectPrivate(q)
71 {
72 }
73 
~QMapObjectViewPrivate()74 QMapObjectViewPrivate::~QMapObjectViewPrivate()
75 {
76 
77 }
78 
type() const79 QGeoMapObject::Type QMapObjectViewPrivate::type() const
80 {
81     return QGeoMapObject::ViewType;
82 }
83 
84 
85 /*
86 
87     QMapObjectViewPrivateDefault
88 
89 */
90 
91 
QMapObjectViewPrivateDefault(const QMapObjectViewPrivate & other)92 QMapObjectViewPrivateDefault::QMapObjectViewPrivateDefault(const QMapObjectViewPrivate &other)
93 :   QMapObjectViewPrivate(other.q), m_model(other.model()), m_delegate(other.delegate())
94 {
95 }
96 
~QMapObjectViewPrivateDefault()97 QMapObjectViewPrivateDefault::~QMapObjectViewPrivateDefault()
98 {
99 
100 }
101 
model() const102 QVariant QMapObjectViewPrivateDefault::model() const
103 {
104     return m_model;
105 }
106 
setModel(const QVariant & model)107 void QMapObjectViewPrivateDefault::setModel(const QVariant &model)
108 {
109     m_model = model;
110 }
111 
delegate() const112 QQmlComponent *QMapObjectViewPrivateDefault::delegate() const
113 {
114     return m_delegate;
115 }
116 
setDelegate(QQmlComponent * delegate)117 void QMapObjectViewPrivateDefault::setDelegate(QQmlComponent *delegate)
118 {
119     m_delegate = delegate;
120 }
121 
QMapObjectViewPrivateDefault(QGeoMapObject * q)122 QMapObjectViewPrivateDefault::QMapObjectViewPrivateDefault(QGeoMapObject *q) : QMapObjectViewPrivate(q)
123 {
124 
125 }
126 
clone()127 QGeoMapObjectPrivate *QMapObjectViewPrivateDefault::clone()
128 {
129     return new QMapObjectViewPrivateDefault(*this);
130 }
131 
equals(const QGeoMapObjectPrivate & other) const132 bool QMapObjectViewPrivateDefault::equals(const QGeoMapObjectPrivate &other) const
133 {
134     if (other.type() != type())
135         return false;
136 
137     const QMapObjectViewPrivate &o = static_cast<const QMapObjectViewPrivate &>(other);
138     return (QGeoMapObjectPrivate::equals(o)
139             && model() == o.model()
140             && delegate() == o.delegate());
141 }
142 
geoShape() const143 QGeoShape QMapObjectViewPrivateDefault::geoShape() const
144 {
145     const QMapObjectView *qq = static_cast<const QMapObjectView *>(q);
146     QGeoRectangle rect;
147     const QList<QGeoMapObject *> kids = qq->geoMapObjectChildren();
148     for (const auto &kid: kids) {
149         if (!rect.isValid())
150             rect = kid->geoShape().boundingGeoRectangle();
151         else
152             rect = rect.united(kid->geoShape().boundingGeoRectangle());
153     }
154     return rect;
155 }
156 
setGeoShape(const QGeoShape &)157 void QMapObjectViewPrivateDefault::setGeoShape(const QGeoShape &/*shape*/)
158 {
159     // MOV doesn't support setting the geoshape.
160 }
161 
162 /*
163 
164     QMapObjectView
165 
166 */
167 
168 
QMapObjectView(QObject * parent)169 QMapObjectView::QMapObjectView(QObject *parent)
170     : QGeoMapObject(QExplicitlySharedDataPointer<QGeoMapObjectPrivate>(new QMapObjectViewPrivateDefault(this)), parent)
171 {
172 
173 }
174 
~QMapObjectView()175 QMapObjectView::~QMapObjectView()
176 {
177     flushDelegateModel();
178     flushUserAddedMapObjects();
179 }
180 
geoMapObjectChildren() const181 QList<QGeoMapObject *> QMapObjectView::geoMapObjectChildren() const
182 {
183     auto kids = QGeoMapObject::geoMapObjectChildren();
184     auto size = m_instantiatedMapObjects.count();
185     for (int i = 0; i < size; ++i) {
186         auto obj = qobject_cast<QGeoMapObject*>(m_instantiatedMapObjects[i]);
187         if (obj)
188             kids << obj;
189     }
190     for (int i = 0; i < m_userAddedMapObjects.size(); ++i) {
191         auto obj = m_userAddedMapObjects.at(i);
192         if (obj)
193             kids << obj;
194     }
195     return kids;
196 }
197 
classBegin()198 void QMapObjectView::classBegin()
199 {
200     QQmlContext *ctx = qmlContext(this);
201     m_delegateModel = new QQmlDelegateModel(ctx, this);
202     m_delegateModel->classBegin();
203 
204     QQmlInstanceModel *model = m_delegateModel;
205     connect(model, &QQmlInstanceModel::modelUpdated, this, &QMapObjectView::modelUpdated);
206     connect(model, &QQmlInstanceModel::createdItem, this, &QMapObjectView::createdItem);
207 //    connect(model, &QQmlInstanceModel::destroyingItem, this, &QMapObjectView::destroyingItem);
208 //    connect(model, &QQmlInstanceModel::initItem, this, &QMapObjectView::initItem);
209 }
210 
componentComplete()211 void QMapObjectView::componentComplete()
212 {
213     QGeoMapObject::componentComplete();
214     QMapObjectViewPrivate *d = static_cast<QMapObjectViewPrivate *>(d_ptr.data());
215     if (d->delegate())
216         m_delegateModel->setDelegate(d->delegate());
217     if (d->model().isValid())
218         m_delegateModel->setModel(d->model());
219     m_delegateModel->componentComplete();
220 }
221 
222 /*!
223     \qmlproperty Variant Qt.labs.location::MapObjectView::model
224 
225     This property holds the model that provides data used for creating the map items defined by the
226     delegate. Only QAbstractItemModel based models are supported.
227 */
model() const228 QVariant QMapObjectView::model() const
229 {
230     const QMapObjectViewPrivate *d = static_cast<const QMapObjectViewPrivate *>(d_ptr.data());
231     return d->model();
232 }
233 
234 /*!
235     \qmlproperty Component Qt.labs.location::MapObjectView::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     QGeoMapObject -derived object as the root object.
240 */
delegate() const241 QQmlComponent *QMapObjectView::delegate() const
242 {
243     const QMapObjectViewPrivate *d = static_cast<const QMapObjectViewPrivate *>(d_ptr.data());
244     return d->delegate();
245 }
246 
setModel(QVariant model)247 void QMapObjectView::setModel(QVariant model)
248 {
249     QMapObjectViewPrivate *d = static_cast<QMapObjectViewPrivate *>(d_ptr.data());
250     if (d->model() == model)
251         return;
252     d->setModel(model);
253 
254     if (d_ptr->m_componentCompleted)
255         m_delegateModel->setModel(model);
256 
257     emit modelChanged(model);
258 }
259 
setDelegate(QQmlComponent * delegate)260 void QMapObjectView::setDelegate(QQmlComponent *delegate)
261 {
262     QMapObjectViewPrivate *d = static_cast<QMapObjectViewPrivate *>(d_ptr.data());
263     if (d->delegate() == delegate)
264         return;
265     d->setDelegate(delegate);
266 
267     if (d_ptr->m_componentCompleted)
268         m_delegateModel->setDelegate(delegate);
269 
270     emit delegateChanged(delegate);
271 }
272 
273 /*!
274     \qmlmethod void Qt.labs.location::MapObjectView::addMapObject(MapObject object)
275 
276     Adds the given \a object to the MapObjectView (for example MapIconObject, MapRouteObject), and,
277     indirectly, to the underlying map. If the object already is on the MapObjectView, it will not be added again.
278 
279     \sa removeMapObject
280 */
addMapObject(QGeoMapObject * object)281 void QMapObjectView::addMapObject(QGeoMapObject *object)
282 {
283     if (m_userAddedMapObjects.indexOf(object) < 0)
284         m_userAddedMapObjects.append(object);
285     if (map() && object->map() != map())
286         object->setMap(map());
287 }
288 
289 /*!
290     \qmlmethod void Qt.labs.location::MapObjectView::removeMapObject(MapObject object)
291 
292     Removes the given \a object from the MapObjectView (for example MapIconObject, MapRouteObject), and,
293     indirectly, from the underlying map.
294 
295     \sa addMapObject
296 */
removeMapObject(QGeoMapObject * object)297 void QMapObjectView::removeMapObject(QGeoMapObject *object)
298 {
299     int idx = m_userAddedMapObjects.indexOf(object);
300     if ( idx >= 0) {
301         object->setMap(nullptr);
302         m_userAddedMapObjects.remove(idx);
303     }
304 }
305 
destroyingItem(QObject *)306 void QMapObjectView::destroyingItem(QObject * /*object*/)
307 {
308 
309 }
310 
initItem(int,QObject *)311 void QMapObjectView::initItem(int /*index*/, QObject * /*object*/)
312 {
313 
314 }
315 
modelUpdated(const QQmlChangeSet & changeSet,bool reset)316 void QMapObjectView::modelUpdated(const QQmlChangeSet &changeSet, bool reset)
317 {
318     // move changes are expressed as one remove + one insert, with the same moveId.
319     // For simplicity, they will be treated as remove + insert.
320     // Changes will be also ignored, as they represent only data changes, not layout changes
321     if (reset) { // Assuming this means "remove everything already instantiated"
322         flushDelegateModel();
323     } else {
324         // Remove map objects from the back to the front to retain the mapping to what is received from the changesets
325         const QVector<QQmlChangeSet::Change> &removes = changeSet.removes();
326         std::map<int, int> mapRemoves;
327         for (int i = 0; i < removes.size(); i++)
328             mapRemoves.insert(std::pair<int, int>(removes.at(i).start(), i));
329 
330         for (auto rit = mapRemoves.rbegin(); rit != mapRemoves.rend(); ++rit) {
331             const QQmlChangeSet::Change &c = removes.at(rit->second);
332             for (int idx = c.end() - 1; idx >= c.start(); --idx)
333                     removeMapObjectFromMap(idx);
334         }
335     }
336 
337     QBoolBlocker createBlocker(m_creatingObject, true);
338     for (const QQmlChangeSet::Change &c: changeSet.inserts()) {
339         for (int idx = c.start(); idx < c.end(); idx++) {
340             m_instantiatedMapObjects.insert(idx, nullptr);
341             QGeoMapObject *mo = qobject_cast<QGeoMapObject *>(m_delegateModel->object(idx, incubationMode));
342             if (mo) {// if not, a createdItem signal will be emitted later, else it has been emitted already while createBlocker is in effect.
343                 mo->setParent(this);
344                 addMapObjectToMap(mo, idx);
345             }
346         }
347     }
348 }
349 
addMapObjectToMap(QGeoMapObject * object,int index)350 void QMapObjectView::addMapObjectToMap(QGeoMapObject *object, int index)
351 {
352     if (!object)
353         return;
354 
355     m_instantiatedMapObjects[index] = object;
356     if (map())
357         object->setMap(map());
358     else
359         m_pendingMapObjects << object;
360 
361     // ToDo:
362     // Figure out the proper way to replace "mo->setVisible(visible());". Options:
363     // - simply leave it to the user to set up a property binding
364     // - set up a property binding automatically
365     // - add a viewVisibility member to QGeoMapObject that gets combined at all times,
366     //   and a connection for it.
367 }
368 
removeMapObjectFromMap(int index)369 void QMapObjectView::removeMapObjectFromMap(int index)
370 {
371     if (index >= 0 && index < m_instantiatedMapObjects.size()) {
372         QGeoMapObject *mo = m_instantiatedMapObjects.takeAt(index);
373         if (!mo)
374             return;
375 
376         mo->setMap(nullptr);
377         QQmlInstanceModel::ReleaseFlags releaseStatus = m_delegateModel->release(mo);
378 #ifdef QT_DEBUG
379     if (releaseStatus == QQmlInstanceModel::Referenced)
380         qWarning() << "object "<<mo<<" still referenced";
381 #else
382     Q_UNUSED(releaseStatus)
383 #endif
384     }
385 }
386 
387 // See QObject *QQmlDelegateModel::object(int index, QQmlIncubator::IncubationMode incubationMode) doc
388 // for explanation on when createdItem is emitted.
createdItem(int index,QObject *)389 void QMapObjectView::createdItem(int index, QObject * /*object*/)
390 {
391     if (m_creatingObject) {
392         // see QDeclarativeGeoMapItemView::createdItem
393         return;
394     }
395 
396     // If here, according to the documentation above, object() should be called again for index,
397     // or else, it will be destroyed exiting this scope
398     QGeoMapObject *mo = nullptr;
399     mo = qobject_cast<QGeoMapObject *>(m_delegateModel->object(index, incubationMode));
400     if (mo) {
401         mo->setParent(this);
402         addMapObjectToMap(mo, index);
403     } else {
404         qWarning() << "QQmlDelegateModel::object called in createdItem for " << index << " produced a null object";
405     }
406 }
407 
408 
flushDelegateModel()409 void QMapObjectView::flushDelegateModel()
410 {
411     // Backward as removeItemFromMap modifies m_instantiatedItems
412     for (int i = m_instantiatedMapObjects.size() -1; i >= 0 ; i--)
413         removeMapObjectFromMap(i);
414 }
415 
flushUserAddedMapObjects()416 void QMapObjectView::flushUserAddedMapObjects()
417 {
418     for (int i = 0; i < m_userAddedMapObjects.size(); ++i) {
419         auto obj = m_userAddedMapObjects.at(i);
420         if (obj)
421             obj->setMap(nullptr); // obj parent might not be this. If so, it would not be destroyed by destroying this view.
422     }
423 }
424 
setMap(QGeoMap * map)425 void QMapObjectView::setMap(QGeoMap *map)
426 {
427     QMapObjectViewPrivate *d = static_cast<QMapObjectViewPrivate *>(d_ptr.data());
428     if (d->m_map == map)
429         return;
430 
431     QGeoMapObject::setMap(map); // This is where the specialized pimpl gets created and injected
432 
433     for (int i = 0; i < m_userAddedMapObjects.size(); ++i) {
434         auto obj = m_userAddedMapObjects.at(i);
435         if (obj && obj->map() != map)
436             obj->setMap(map);
437     }
438 
439     if (!map) {
440         // Map was set, now it has ben re-set to NULL
441         flushDelegateModel();
442         flushUserAddedMapObjects();
443         bool oldVisible = d_ptr->m_visible;
444         bool oldCmponentCompleted = d_ptr->m_componentCompleted;
445         d_ptr = new QMapObjectViewPrivateDefault(*d);
446         d_ptr->m_componentCompleted = oldCmponentCompleted;
447         d_ptr->setVisible(oldVisible);
448     } else if (d->m_componentCompleted) {
449         // Map was null, now it's set AND delegateModel is already complete.
450         // some delegates may have been incubated but not added to the map.
451         for (int i = 0; i < m_pendingMapObjects.size(); ++i) {
452             auto obj = m_pendingMapObjects.at(i);
453             if (obj && obj->map() != map)
454                 obj->setMap(map);
455         }
456         m_pendingMapObjects.clear();
457     }
458 }
459 
460 QT_END_NAMESPACE
461 
462