Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qdeclarativesupportedcategoriesmodel.cpp
Go to the documentation of this file.
1// Copyright (C) 2014 Aaron McCarthy <mccarthy.aaron@gmail.com>
2// Copyright (C) 2022 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
8#include <QtCore/private/qobject_p.h>
9
10#include <QCoreApplication>
11#include <QtQml/QQmlInfo>
12#include <QtLocation/QPlaceManager>
13#include <QtLocation/QPlaceIcon>
14
16
17/*!
18 \qmltype CategoryModel
19 \nativetype QDeclarativeSupportedCategoriesModel
20 \inqmlmodule QtLocation
21 \ingroup qml-QtLocation5-places
22 \ingroup qml-QtLocation5-places-models
23 \since QtLocation 5.5
24
25 \brief The CategoryModel type provides a model of the categories supported by a \l Plugin.
26
27 The CategoryModel type provides a model of the categories that are available from the
28 current \l Plugin. The model supports both a flat list of categories and a hierarchical tree
29 representing category groupings. This can be controlled by the \l hierarchical property.
30
31 The model supports the following roles:
32
33 \table
34 \header
35 \li Role
36 \li Type
37 \li Description
38 \row
39 \li category
40 \li \l Category
41 \li Category object for the current item.
42 \row
43 \li parentCategory
44 \li \l Category
45 \li Parent category object for the current item.
46 If there is no parent, null is returned.
47 \endtable
48
49 The following example displays a flat list of all available categories:
50
51 \snippet declarative/places.qml QtQuick import
52 \snippet declarative/maps.qml QtLocation import
53 \codeline
54 \snippet declarative/places.qml CategoryView
55
56 To access the hierarchical category model it is necessary to use a \l DelegateModel to access
57 the child items.
58*/
59
60/*!
61 \qmlproperty Plugin CategoryModel::plugin
62
63 This property holds the provider \l Plugin used by this model.
64*/
65
66/*!
67 \qmlproperty bool CategoryModel::hierarchical
68
69 This property holds whether the model provides a hierarchical tree of categories or a flat
70 list. The default is true.
71*/
72
73/*!
74 \qmlmethod string QtLocation::CategoryModel::data(ModelIndex index, int role)
75 \internal
76
77 This method retrieves the model's data per \a index and \a role.
78*/
79
80/*!
81 \qmlmethod string QtLocation::CategoryModel::errorString() const
82
83 This read-only property holds the textual presentation of the latest category model error.
84 If no error has occurred, an empty string is returned.
85
86 An empty string may also be returned if an error occurred which has no associated
87 textual representation.
88*/
89
90/*!
91 \qmlmethod void QtLocation::CategoryModel::update()
92 \internal
93
94 Updates the model.
95
96 \note The CategoryModel auto updates automatically when needed. Calling this method explicitly is normally not necessary.
97*/
98
99/*!
100 \internal
101 \enum QDeclarativeSupportedCategoriesModel::Roles
102*/
103
104QDeclarativeSupportedCategoriesModel::QDeclarativeSupportedCategoriesModel(QObject *parent)
105 : QAbstractItemModel(parent)
106{
107}
108
109QDeclarativeSupportedCategoriesModel::~QDeclarativeSupportedCategoriesModel()
110{
111 qDeleteAll(m_categoriesTree);
112}
113
114/*!
115 \internal
116*/
117// From QQmlParserStatus
118void QDeclarativeSupportedCategoriesModel::componentComplete()
119{
120 m_complete = true;
121 if (m_plugin) // do not try to load or change status when trying to update in componentComplete() if the plugin hasn't been set yet even once.
122 update();
123}
124
125/*!
126 \internal
127*/
128int QDeclarativeSupportedCategoriesModel::rowCount(const QModelIndex &parent) const
129{
130 if (m_categoriesTree.keys().isEmpty())
131 return 0;
132
133 PlaceCategoryNode *node = static_cast<PlaceCategoryNode *>(parent.internalPointer());
134 if (!node)
135 node = m_categoriesTree.value(QString());
136 else if (m_categoriesTree.keys(node).isEmpty())
137 return 0;
138
139 return node->childIds.count();
140}
141
142/*!
143 \internal
144*/
145int QDeclarativeSupportedCategoriesModel::columnCount(const QModelIndex &parent) const
146{
147 Q_UNUSED(parent);
148
149 return 1;
150}
151
152/*!
153 \internal
154*/
155QModelIndex QDeclarativeSupportedCategoriesModel::index(int row, int column, const QModelIndex &parent) const
156{
157 if (column != 0 || row < 0)
158 return QModelIndex();
159
160 PlaceCategoryNode *node = static_cast<PlaceCategoryNode *>(parent.internalPointer());
161
162 if (!node)
163 node = m_categoriesTree.value(QString());
164 else if (m_categoriesTree.keys(node).isEmpty()) //return root index if parent is non-existent
165 return QModelIndex();
166
167 if (row > node->childIds.count())
168 return QModelIndex();
169
170 QString id = node->childIds.at(row);
171 Q_ASSERT(m_categoriesTree.contains(id));
172
173 return createIndex(row, 0, m_categoriesTree.value(id));
174}
175
176/*!
177 \internal
178*/
179QModelIndex QDeclarativeSupportedCategoriesModel::parent(const QModelIndex &child) const
180{
181 PlaceCategoryNode *childNode = static_cast<PlaceCategoryNode *>(child.internalPointer());
182 if (m_categoriesTree.keys(childNode).isEmpty())
183 return QModelIndex();
184
185 return index(childNode->parentId);
186}
187
188/*!
189 \internal
190*/
191QVariant QDeclarativeSupportedCategoriesModel::data(const QModelIndex &index, int role) const
192{
193 PlaceCategoryNode *node = static_cast<PlaceCategoryNode *>(index.internalPointer());
194 if (!node)
195 node = m_categoriesTree.value(QString());
196 else if (m_categoriesTree.keys(node).isEmpty())
197 return QVariant();
198
199 QDeclarativeCategory *category = node->declCategory.data();
200
201 switch (role) {
202 case Qt::DisplayRole:
203 return category->name();
204 case CategoryRole:
205 return QVariant::fromValue(category);
206 case ParentCategoryRole: {
207 if (!m_categoriesTree.keys().contains(node->parentId))
208 return QVariant();
209 else
210 return QVariant::fromValue(m_categoriesTree.value(node->parentId)->declCategory.data());
211 }
212 default:
213 return QVariant();
214 }
215}
216
217QHash<int, QByteArray> QDeclarativeSupportedCategoriesModel::roleNames() const
218{
219 QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
220 roles.insert(CategoryRole, "category");
221 roles.insert(ParentCategoryRole, "parentCategory");
222 return roles;
223}
224
225/*!
226 \internal
227*/
228void QDeclarativeSupportedCategoriesModel::setPlugin(QDeclarativeGeoServiceProvider *plugin)
229{
230 if (m_plugin == plugin)
231 return;
232
233 //disconnect the manager of the old plugin if we have one
234 if (m_plugin) {
235 disconnect(m_plugin, nullptr, this, nullptr);
236 QGeoServiceProvider *serviceProvider = m_plugin->sharedGeoServiceProvider();
237 if (serviceProvider) {
238 QPlaceManager *placeManager = serviceProvider->placeManager();
239 if (placeManager) {
240 disconnect(placeManager, &QPlaceManager::categoryAdded,
241 this, &QDeclarativeSupportedCategoriesModel::addedCategory);
242 disconnect(placeManager, &QPlaceManager::categoryUpdated,
243 this, &QDeclarativeSupportedCategoriesModel::updatedCategory);
244 disconnect(placeManager, &QPlaceManager::categoryRemoved,
245 this, &QDeclarativeSupportedCategoriesModel::removedCategory);
246 disconnect(placeManager, &QPlaceManager::dataChanged,
247 this, &QDeclarativeSupportedCategoriesModel::emitDataChanged);
248 }
249 }
250 }
251
252 m_plugin = plugin;
253
254 // handle plugin attached changes -> update categories
255 if (m_plugin) {
256 if (m_plugin->isAttached()) {
257 connectNotificationSignals();
258 update();
259 } else {
260 connect(m_plugin, &QDeclarativeGeoServiceProvider::attached,
261 this, &QDeclarativeSupportedCategoriesModel::update);
262 connect(m_plugin, &QDeclarativeGeoServiceProvider::attached,
263 this, &QDeclarativeSupportedCategoriesModel::connectNotificationSignals);
264 }
265 }
266
267 if (m_complete)
268 emit pluginChanged();
269}
270
271/*!
272 \internal
273*/
274QDeclarativeGeoServiceProvider *QDeclarativeSupportedCategoriesModel::plugin() const
275{
276 return m_plugin;
277}
278
279/*!
280 \internal
281*/
282void QDeclarativeSupportedCategoriesModel::setHierarchical(bool hierarchical)
283{
284 if (m_hierarchical == hierarchical)
285 return;
286
287 m_hierarchical = hierarchical;
288 emit hierarchicalChanged();
289
290 updateLayout();
291}
292
293/*!
294 \internal
295*/
296bool QDeclarativeSupportedCategoriesModel::hierarchical() const
297{
298 return m_hierarchical;
299}
300
301/*!
302 \internal
303*/
304void QDeclarativeSupportedCategoriesModel::replyFinished()
305{
306 if (!m_response)
307 return;
308
309 m_response->deleteLater();
310
311 if (m_response->error() == QPlaceReply::NoError) {
312 m_errorString.clear();
313
314 m_response = nullptr;
315
316 updateLayout();
317 setStatus(QDeclarativeSupportedCategoriesModel::Ready);
318 } else {
319 const QString errorString = m_response->errorString();
320
321 m_response = nullptr;
322
323 setStatus(Error, errorString);
324 }
325}
326
327/*!
328 \internal
329*/
330void QDeclarativeSupportedCategoriesModel::addedCategory(const QPlaceCategory &category,
331 const QString &parentId)
332{
333 if (m_response)
334 return;
335
336 if (!m_categoriesTree.contains(parentId))
337 return;
338
339 if (category.categoryId().isEmpty())
340 return;
341
342 PlaceCategoryNode *parentNode = m_categoriesTree.value(parentId);
343 if (!parentNode)
344 return;
345
346 int rowToBeAdded = rowToAddChild(parentNode, category);
347 QModelIndex parentIndex = index(parentId);
348 beginInsertRows(parentIndex, rowToBeAdded, rowToBeAdded);
349 PlaceCategoryNode *categoryNode = new PlaceCategoryNode;
350 categoryNode->parentId = parentId;
351 categoryNode->declCategory = QSharedPointer<QDeclarativeCategory>(new QDeclarativeCategory(category, m_plugin, this));
352
353 m_categoriesTree.insert(category.categoryId(), categoryNode);
354 parentNode->childIds.insert(rowToBeAdded,category.categoryId());
355 endInsertRows();
356
357 //this is a workaround to deal with the fact that the hasModelChildren field of DelegateModel
358 //does not get updated when a child is added to a model
359 beginResetModel();
360 endResetModel();
361}
362
363/*!
364 \internal
365*/
366void QDeclarativeSupportedCategoriesModel::updatedCategory(const QPlaceCategory &category,
367 const QString &parentId)
368{
369 if (m_response)
370 return;
371
372 QString categoryId = category.categoryId();
373
374 if (!m_categoriesTree.contains(parentId))
375 return;
376
377 if (category.categoryId().isEmpty() || !m_categoriesTree.contains(categoryId))
378 return;
379
380 PlaceCategoryNode *newParentNode = m_categoriesTree.value(parentId);
381 if (!newParentNode)
382 return;
383
384 PlaceCategoryNode *categoryNode = m_categoriesTree.value(categoryId);
385 if (!categoryNode)
386 return;
387
388 categoryNode->declCategory->setCategory(category);
389
390 if (categoryNode->parentId == parentId) { //reparenting to same parent
391 QModelIndex parentIndex = index(parentId);
392 int rowToBeAdded = rowToAddChild(newParentNode, category);
393 int oldRow = newParentNode->childIds.indexOf(categoryId);
394
395 //check if we are changing the position of the category
396 if (qAbs(rowToBeAdded - newParentNode->childIds.indexOf(categoryId)) > 1) {
397 //if the position has changed we are moving rows
398 beginMoveRows(parentIndex, oldRow, oldRow,
399 parentIndex, rowToBeAdded);
400
401 newParentNode->childIds.removeAll(categoryId);
402 newParentNode->childIds.insert(rowToBeAdded, categoryId);
403 endMoveRows();
404 } else {// if the position has not changed we modifying an existing row
405 QModelIndex categoryIndex = index(categoryId);
406 emit dataChanged(categoryIndex, categoryIndex);
407 }
408 } else { //reparenting to different parents
409 QPlaceCategory oldCategory = categoryNode->declCategory->category();
410 PlaceCategoryNode *oldParentNode = m_categoriesTree.value(categoryNode->parentId);
411 if (!oldParentNode)
412 return;
413 QModelIndex oldParentIndex = index(categoryNode->parentId);
414 QModelIndex newParentIndex = index(parentId);
415
416 int rowToBeAdded = rowToAddChild(newParentNode, category);
417 beginMoveRows(oldParentIndex, oldParentNode->childIds.indexOf(categoryId),
418 oldParentNode->childIds.indexOf(categoryId), newParentIndex, rowToBeAdded);
419 oldParentNode->childIds.removeAll(oldCategory.categoryId());
420 newParentNode->childIds.insert(rowToBeAdded, categoryId);
421 categoryNode->parentId = parentId;
422 endMoveRows();
423
424 //this is a workaround to deal with the fact that the hasModelChildren field of DelegateModel
425 //does not get updated when an index is updated to contain children
426 beginResetModel();
427 endResetModel();
428 }
429}
430
431/*!
432 \internal
433*/
434void QDeclarativeSupportedCategoriesModel::removedCategory(const QString &categoryId, const QString &parentId)
435{
436 if (m_response)
437 return;
438
439 if (!m_categoriesTree.contains(categoryId) || !m_categoriesTree.contains(parentId))
440 return;
441
442 QModelIndex parentIndex = index(parentId);
443 QModelIndex categoryIndex = index(categoryId);
444
445 beginRemoveRows(parentIndex, categoryIndex.row(), categoryIndex.row());
446 PlaceCategoryNode *parentNode = m_categoriesTree.value(parentId);
447 parentNode->childIds.removeAll(categoryId);
448 delete m_categoriesTree.take(categoryId);
449 endRemoveRows();
450}
451
452/*!
453 \internal
454*/
455void QDeclarativeSupportedCategoriesModel::connectNotificationSignals()
456{
457 if (!m_plugin)
458 return;
459
460 QGeoServiceProvider *serviceProvider = m_plugin->sharedGeoServiceProvider();
461 if (!serviceProvider || serviceProvider->error() != QGeoServiceProvider::NoError)
462 return;
463
464 QPlaceManager *placeManager = serviceProvider->placeManager();
465 if (!placeManager)
466 return;
467
468 // listen for any category notifications so that we can reupdate the categories
469 // model.
470 connect(placeManager, &QPlaceManager::categoryAdded,
471 this, &QDeclarativeSupportedCategoriesModel::addedCategory);
472 connect(placeManager, &QPlaceManager::categoryUpdated,
473 this, &QDeclarativeSupportedCategoriesModel::updatedCategory);
474 connect(placeManager, &QPlaceManager::categoryRemoved,
475 this, &QDeclarativeSupportedCategoriesModel::removedCategory);
476 connect(placeManager, &QPlaceManager::dataChanged,
477 this, &QDeclarativeSupportedCategoriesModel::emitDataChanged);
478}
479
480/*!
481 \internal
482*/
483void QDeclarativeSupportedCategoriesModel::update()
484{
485 if (!m_complete)
486 return;
487
488 if (m_response)
489 return;
490
491 setStatus(Loading);
492
493 if (!m_plugin) {
494 updateLayout();
495 setStatus(Error, QCoreApplication::translate(CONTEXT_NAME, PLUGIN_PROPERTY_NOT_SET));
496 return;
497 }
498
499 QGeoServiceProvider *serviceProvider = m_plugin->sharedGeoServiceProvider();
500 if (!serviceProvider || serviceProvider->error() != QGeoServiceProvider::NoError) {
501 updateLayout();
502 setStatus(Error, QCoreApplication::translate(CONTEXT_NAME, PLUGIN_PROVIDER_ERROR)
503 .arg(m_plugin->name()));
504 return;
505 }
506
507 QPlaceManager *placeManager = serviceProvider->placeManager();
508 if (!placeManager) {
509 updateLayout();
510 setStatus(Error, QCoreApplication::translate(CONTEXT_NAME, PLUGIN_ERROR)
511 .arg(m_plugin->name()).arg(serviceProvider->errorString()));
512 return;
513 }
514
515 m_response = placeManager->initializeCategories();
516 if (m_response) {
517 connect(m_response, &QPlaceReply::finished,
518 this, &QDeclarativeSupportedCategoriesModel::replyFinished);
519 } else {
520 updateLayout();
521 setStatus(Error, QCoreApplication::translate(CONTEXT_NAME,
522 CATEGORIES_NOT_INITIALIZED));
523 }
524}
525
526/*!
527 \internal
528*/
529void QDeclarativeSupportedCategoriesModel::updateLayout()
530{
531 beginResetModel();
532 qDeleteAll(m_categoriesTree);
533 m_categoriesTree.clear();
534
535 if (m_plugin) {
536 QGeoServiceProvider *serviceProvider = m_plugin->sharedGeoServiceProvider();
537 if (serviceProvider && serviceProvider->error() == QGeoServiceProvider::NoError) {
538 QPlaceManager *placeManager = serviceProvider->placeManager();
539 if (placeManager) {
540 PlaceCategoryNode *node = new PlaceCategoryNode;
541 node->childIds = populateCategories(placeManager, QPlaceCategory());
542 m_categoriesTree.insert(QString(), node);
543 node->declCategory = QSharedPointer<QDeclarativeCategory>
544 (new QDeclarativeCategory(QPlaceCategory(), m_plugin, this));
545 }
546 }
547 }
548
549 endResetModel();
550}
551
552QString QDeclarativeSupportedCategoriesModel::errorString() const
553{
554 return m_errorString;
555}
556
557/*!
558 \qmlproperty enumeration CategoryModel::status
559
560 This property holds the status of the model. It can be one of:
561
562 \table
563 \row
564 \li CategoryModel.Null
565 \li No category fetch query has been executed. The model is empty.
566 \row
567 \li CategoryModel.Ready
568 \li No error occurred during the last operation, further operations may be performed on
569 the model.
570 \row
571 \li CategoryModel.Loading
572 \li The model is being updated, no other operations may be performed until complete.
573 \row
574 \li CategoryModel.Error
575 \li An error occurred during the last operation, further operations can still be
576 performed on the model.
577 \endtable
578*/
579void QDeclarativeSupportedCategoriesModel::setStatus(Status status, const QString &errorString)
580{
581 Status originalStatus = m_status;
582 m_status = status;
583 m_errorString = errorString;
584
585 if (originalStatus != m_status)
586 emit statusChanged();
587}
588
589QDeclarativeSupportedCategoriesModel::Status QDeclarativeSupportedCategoriesModel::status() const
590{
591 return m_status;
592}
593
594/*!
595 \internal
596*/
597QStringList QDeclarativeSupportedCategoriesModel::populateCategories(QPlaceManager *manager, const QPlaceCategory &parent)
598{
599 Q_ASSERT(manager);
600
601 QStringList childIds;
602
603 const auto byName = [](const QPlaceCategory &lhs, const QPlaceCategory &rhs) {
604 return lhs.name() < rhs.name();
605 };
606
607 auto categories = manager->childCategories(parent.categoryId());
608 std::sort(categories.begin(), categories.end(), byName);
609
610 for (const auto &category : std::as_const(categories)) {
611 auto node = new PlaceCategoryNode;
612 node->parentId = parent.categoryId();
613 node->declCategory = QSharedPointer<QDeclarativeCategory>(new QDeclarativeCategory(category, m_plugin ,this));
614
615 if (m_hierarchical)
616 node->childIds = populateCategories(manager, category);
617
618 m_categoriesTree.insert(node->declCategory->categoryId(), node);
619 childIds.append(category.categoryId());
620
621 if (!m_hierarchical) {
622 childIds.append(populateCategories(manager,node->declCategory->category()));
623 }
624 }
625 return childIds;
626}
627
628/*!
629 \internal
630*/
631QModelIndex QDeclarativeSupportedCategoriesModel::index(const QString &categoryId) const
632{
633 if (categoryId.isEmpty())
634 return QModelIndex();
635
636 if (!m_categoriesTree.contains(categoryId))
637 return QModelIndex();
638
639 PlaceCategoryNode *categoryNode = m_categoriesTree.value(categoryId);
640 if (!categoryNode)
641 return QModelIndex();
642
643 QString parentCategoryId = categoryNode->parentId;
644
645 PlaceCategoryNode *parentNode = m_categoriesTree.value(parentCategoryId);
646
647 return createIndex(parentNode->childIds.indexOf(categoryId), 0, categoryNode);
648}
649
650/*!
651 \internal
652*/
653int QDeclarativeSupportedCategoriesModel::rowToAddChild(PlaceCategoryNode *node, const QPlaceCategory &category)
654{
655 Q_ASSERT(node);
656 for (qsizetype i = 0; i < node->childIds.count(); ++i) {
657 if (category.name() < m_categoriesTree.value(node->childIds.at(i))->declCategory->name())
658 return i;
659 }
660 return node->childIds.count();
661}
662
663/*!
664 \qmlsignal CategoryModel::dataChanged()
665
666 This signal is emitted when significant changes have been made to the underlying datastore.
667
668 Applications should act on this signal at their own discretion. The data
669 provided by the model could be out of date and so the model should be reupdated
670 sometime, however an immediate reupdate may be disconcerting to users if the categories
671 change without any action on their part.
672
673 The corresponding handler is \c onDataChanged.
674*/
675
676QT_END_NAMESPACE