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