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
qdeclarativesearchresultmodel.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
6
7#include <QtQml/QQmlEngine>
8#include <QtQml/QQmlInfo>
9#include <QtLocation/QGeoServiceProvider>
10#include <QtLocation/QPlaceSearchReply>
11#include <QtLocation/QPlaceManager>
12#include <QtLocation/QPlaceMatchRequest>
13#include <QtLocation/QPlaceMatchReply>
14#include <QtLocation/QPlaceResult>
15#include <QtLocation/QPlaceProposedSearchResult>
16#include <QtLocation/private/qplacesearchrequest_p.h>
17
18QT_BEGIN_NAMESPACE
19
20/*!
21 \qmltype PlaceSearchModel
22 \nativetype QDeclarativeSearchResultModel
23 \inqmlmodule QtLocation
24 \ingroup qml-QtLocation5-places
25 \ingroup qml-QtLocation5-places-models
26 \since QtLocation 5.5
27
28 \brief Provides access to place search results.
29
30 PlaceSearchModel provides a model of place search results within the \l searchArea. The
31 \l searchTerm and \l categories properties can be set to restrict the search results to
32 places matching those criteria.
33
34 The PlaceSearchModel returns both sponsored and
35 \l {http://en.wikipedia.org/wiki/Organic_search}{organic search results}. Sponsored search
36 results will have the \c sponsored role set to true.
37
38 \target PlaceSearchModel Roles
39 The model returns data for the following roles:
40
41 \table
42 \header
43 \li Role
44 \li Type
45 \li Description
46 \row
47 \li type
48 \li enum
49 \li The type of search result.
50 \row
51 \li title
52 \li string
53 \li A string describing the search result.
54 \row
55 \li icon
56 \li PlaceIcon
57 \li Icon representing the search result.
58 \row
59 \li distance
60 \li real
61 \li Valid only when the \c type role is \c PlaceResult, the distance to the place
62 from the center of the \l searchArea. If no \l searchArea
63 has been specified, the distance is NaN.
64 \row
65 \li place
66 \li \l Place
67 \li Valid only when the \c type role is \c PlaceResult, an object representing the
68 place.
69 \row
70 \li sponsored
71 \li bool
72 \li Valid only when the \c type role is \c PlaceResult, true if the search result is a
73 sponsored result.
74 \endtable
75
76 \section2 Search Result Types
77
78 The \c type role can take on the following values:
79
80 \table
81 \row
82 \li PlaceSearchModel.UnknownSearchResult
83 \li The contents of the search result are unknown.
84 \row
85 \li PlaceSearchModel.PlaceResult
86 \li The search result contains a place.
87 \row
88 \li PlaceSearchModel.ProposedSearchResult
89 \li The search result contains a proposed search which may be relevant.
90 \endtable
91
92
93 It can often be helpful to use a \l Loader to create a delegate
94 that will choose different \l {Component}s based on the search result type.
95
96 \snippet declarative/places_loader.qml Handle Result Types
97
98 \section1 Detection of Updated and Removed Places
99
100 The PlaceSearchModel listens for places that have been updated or removed from its plugin's backend.
101 If it detects that a place has been updated and that place is currently present in the model, then
102 it will call \l Place::getDetails to refresh the details. If it detects that a place has been
103 removed, then correspondingly the place will be removed from the model if it is currently
104 present.
105
106 \section1 Example
107
108 The following example shows how to use the PlaceSearchModel to search for Pizza restaurants in
109 close proximity of a given position. A \l searchTerm and \l searchArea are provided to the model
110 and \l update() is used to perform a lookup query. Note that the model does not incrementally
111 fetch search results, but rather performs a single fetch when \l update() is run. The \l count
112 is set to the number of search results returned during the fetch.
113
114 \snippet places_list/places_list.qml Imports
115 \codeline
116 \snippet places_list/places_list.qml PlaceSearchModel
117
118 \sa CategoryModel, {QPlaceManager}
119
120 \section1 Paging
121 The PlaceSearchModel API has some limited support
122 for paging. The \l nextPage() and \l previousPage() functions as well as
123 the \l limit property can be used to access
124 paged search results. When the \l limit property is set
125 the search result page contains at most \l limit entries (of type place result).
126 For example, if the backend has 5 search results in total
127 [a,b,c,d,e], and assuming the first page is shown and limit of 3 has been set
128 then a,b,c is returned. The \l nextPage() would return d,e. The
129 \l nextPagesAvailable and \l previousPagesAvailable properties
130 can be used to check for further pages. At the moment the API does not
131 support the means to retrieve the total number of items available from the
132 backed. Note that support for \l nextPage(), previousPage() and \l limit can vary
133 according to the \l plugin.
134*/
135
136/*!
137 \qmlproperty Plugin PlaceSearchModel::plugin
138
139 This property holds the \l Plugin which will be used to perform the search.
140*/
141
142/*!
143 \qmlproperty Plugin PlaceSearchModel::favoritesPlugin
144
145 This property holds the \l Plugin which will be used to search for favorites.
146 Any places from the search which can be cross-referenced or matched
147 in the favoritesPlugin will have their \l {Place::favorite}{favorite} property
148 set to the corresponding \l Place from the favoritesPlugin.
149
150 If the favoritesPlugin is not set, the \l {Place::favorite}{favorite} property
151 of the places in the results will always be null.
152
153 \sa Favorites
154*/
155
156/*!
157 \qmlproperty VariantMap PlaceSearchModel::favoritesMatchParameters
158
159 This property holds a set of parameters used to specify how search result places
160 are matched to favorites in the favoritesPlugin.
161
162 By default the parameter map is empty and implies that the favorites plugin
163 matches by \l {Alternative Identifier Cross-Referencing}{alternative identifiers}. Generally,
164 an application developer will not need to set this property.
165
166 In cases where the favorites plugin does not support matching by alternative identifiers,
167 then the \l {Qt Location#Plugin References and Parameters}{plugin documentation} should
168 be consulted to see precisely what key-value parameters to set.
169*/
170
171/*!
172 \qmlproperty variant PlaceSearchModel::searchArea
173
174 This property holds the search area. The search result returned by the model will be within
175 the search area.
176
177 If this property is set to a \l {geocircle} its
178 \l {geocircle}{radius} property may be left unset, in which case the \l Plugin
179 will choose an appropriate radius for the search.
180
181 Support for specifying a search area can vary according to the \l plugin backend
182 implementation. For example, some may support a search center only while others may only
183 support geo rectangles.
184*/
185
186/*!
187 \qmlproperty int PlaceSearchModel::limit
188
189 This property holds the limit of the number of items that will be returned.
190*/
191
192/*!
193 \qmlproperty bool PlaceSearchModel::previousPagesAvailable
194
195 This property holds whether there is one or more previous pages of search results available.
196
197 \sa previousPage()
198*/
199
200/*!
201 \qmlproperty bool PlaceSearchModel::nextPagesAvailable
202
203 This property holds whether there is one or more additional pages of search results available.
204
205 \sa nextPage()
206*/
207
208/*!
209 \qmlproperty enum PlaceSearchModel::status
210
211 This property holds the status of the model. It can be one of:
212
213 \table
214 \row
215 \li PlaceSearchModel.Null
216 \li No search query has been executed. The model is empty.
217 \row
218 \li PlaceSearchModel.Ready
219 \li The search query has completed, and the results are available.
220 \row
221 \li PlaceSearchModel.Loading
222 \li A search query is currently being executed.
223 \row
224 \li PlaceSearchModel.Error
225 \li An error occurred when executing the previous search query.
226 \endtable
227*/
228
229/*!
230 \qmlproperty bool PlaceSearchModel::incremental
231
232 This property controls how paging will affect the PlaceSearchModel.
233 If true, calling \l previousPage or \l nextPage will not reset the model,
234 but new results will instead be appended to the model.
235 Default is false.
236
237 \since QtLocation 5.12
238*/
239
240
241/*!
242 \qmlmethod void PlaceSearchModel::update()
243
244 Updates the model based on the provided query parameters. The model will be populated with a
245 list of places matching the search parameters specified by the type's properties. Search
246 criteria is specified by setting properties such as the \l searchTerm, \l categories, \l searchArea and \l limit.
247 Support for these properties may vary according to \l plugin. \c update() then
248 submits the set of criteria to the \l plugin to process.
249
250 While the model is updating the \l status of the model is set to
251 \c PlaceSearchModel.Loading. If the model is successfully updated the \l status is set to
252 \c PlaceSearchModel.Ready, while if it unsuccessfully completes, the \l status is set to
253 \c PlaceSearchModel.Error and the model cleared.
254
255 \code
256 PlaceSearchModel {
257 id: model
258 plugin: backendPlugin
259 searchArea: QtPositioning.circle(QtPositioning.coordinate(10, 10))
260 ...
261 }
262
263 MouseArea {
264 ...
265 onClicked: {
266 model.searchTerm = "pizza";
267 model.categories = null; //not searching by any category
268 model.searchArea.center.latitude = -27.5;
269 model.searchArea.center.longitude = 153;
270 model.update();
271 }
272 }
273 \endcode
274
275 \sa cancel(), status
276*/
277
278/*!
279 \qmlmethod void PlaceSearchModel::cancel()
280
281 Cancels an ongoing search operation immediately and sets the model
282 status to PlaceSearchModel.Ready. The model retains any search
283 results it had before the operation was started.
284
285 If an operation is not ongoing, invoking cancel() has no effect.
286
287 \sa update(), status
288*/
289
290/*!
291 \qmlmethod void PlaceSearchModel::reset()
292
293 Resets the model. All search results are cleared, any outstanding requests are aborted and
294 possible errors are cleared. Model status will be set to PlaceSearchModel.Null.
295*/
296
297/*!
298 \qmlmethod string PlaceSearchModel::errorString() const
299
300 This read-only property holds the textual presentation of the latest place search model error.
301 If no error has occurred or if the model was cleared, an empty string is returned.
302
303 An empty string may also be returned if an error occurred which has no associated
304 textual representation.
305*/
306
307/*!
308 \qmlmethod void PlaceSearchModel::previousPage()
309
310 Updates the model to display the previous page of search results. If there is no previous page
311 then this method does nothing.
312*/
313
314/*!
315 \qmlmethod void PlaceSearchModel::nextPage()
316
317 Updates the model to display the next page of search results. If there is no next page then
318 this method does nothing.
319*/
320
321QDeclarativeSearchResultModel::QDeclarativeSearchResultModel(QObject *parent)
322 : QDeclarativeSearchModelBase(parent)
323{
324}
325
326QDeclarativeSearchResultModel::~QDeclarativeSearchResultModel()
327{
328}
329
330/*!
331 \qmlproperty string PlaceSearchModel::searchTerm
332
333 This property holds search term used in query. The search term is a free-form text string.
334*/
335QString QDeclarativeSearchResultModel::searchTerm() const
336{
337 return m_request.searchTerm();
338}
339
340void QDeclarativeSearchResultModel::setSearchTerm(const QString &searchTerm)
341{
342 m_request.setSearchContext(QVariant());
343
344 if (m_request.searchTerm() == searchTerm)
345 return;
346
347 m_request.setSearchTerm(searchTerm);
348 emit searchTermChanged();
349}
350
351/*!
352 \qmlproperty list<Category> PlaceSearchModel::categories
353
354 This property holds a list of categories to be used when searching. Returned search results
355 will be for places that match at least one of the categories.
356*/
357QQmlListProperty<QDeclarativeCategory> QDeclarativeSearchResultModel::categories()
358{
359 return QQmlListProperty<QDeclarativeCategory>(this,
360 0, // opaque data parameter
361 categories_append,
362 categories_count,
363 category_at,
364 categories_clear);
365}
366
367void QDeclarativeSearchResultModel::categories_append(QQmlListProperty<QDeclarativeCategory> *list,
368 QDeclarativeCategory *declCategory)
369{
370 QDeclarativeSearchResultModel *searchModel = qobject_cast<QDeclarativeSearchResultModel *>(list->object);
371 if (searchModel && declCategory) {
372 searchModel->m_request.setSearchContext(QVariant());
373 searchModel->m_categories.append(declCategory);
374 QList<QPlaceCategory> categories = searchModel->m_request.categories();
375 categories.append(declCategory->category());
376 searchModel->m_request.setCategories(categories);
377 emit searchModel->categoriesChanged();
378 }
379}
380
381qsizetype QDeclarativeSearchResultModel::categories_count(QQmlListProperty<QDeclarativeCategory> *list)
382{
383 QDeclarativeSearchResultModel *searchModel = qobject_cast<QDeclarativeSearchResultModel *>(list->object);
384 if (searchModel)
385 return searchModel->m_categories.count();
386 else
387 return -1;
388}
389
390QDeclarativeCategory *QDeclarativeSearchResultModel::category_at(QQmlListProperty<QDeclarativeCategory> *list,
391 qsizetype index)
392{
393 QDeclarativeSearchResultModel *searchModel = qobject_cast<QDeclarativeSearchResultModel *>(list->object);
394 if (searchModel && (searchModel->m_categories.count() > index) && (index > -1))
395 return searchModel->m_categories.at(index);
396 return nullptr;
397}
398
399void QDeclarativeSearchResultModel::categories_clear(QQmlListProperty<QDeclarativeCategory> *list)
400{
401 QDeclarativeSearchResultModel *searchModel = qobject_cast<QDeclarativeSearchResultModel *>(list->object);
402 if (searchModel) {
403 //note: we do not need to delete each of the objects in m_categories since the search model
404 //should never be the parent of the categories anyway.
405 searchModel->m_request.setSearchContext(QVariant());
406 searchModel->m_categories.clear();
407 searchModel->m_request.setCategories(QList<QPlaceCategory>());
408 emit searchModel->categoriesChanged();
409 }
410}
411
412/*!
413 \qmlproperty string PlaceSearchModel::recommendationId
414
415 This property holds the placeId to be used in order to find recommendations
416 for similar places.
417*/
418QString QDeclarativeSearchResultModel::recommendationId() const
419{
420 return m_request.recommendationId();
421}
422
423void QDeclarativeSearchResultModel::setRecommendationId(const QString &placeId)
424{
425 if (m_request.recommendationId() == placeId)
426 return;
427
428 m_request.setRecommendationId(placeId);
429 emit recommendationIdChanged();
430}
431
432/*!
433 \qmlproperty enumeration PlaceSearchModel::relevanceHint
434
435 This property holds a relevance hint used in the search query. The hint is given to the
436 provider to help but not dictate the ranking of results. For example, the distance hint may
437 give closer places a higher ranking but it does not necessarily mean the results will be
438 strictly ordered according to distance. A provider may ignore the hint altogether.
439
440 \table
441 \row
442 \li SearchResultModel.UnspecifiedHint
443 \li No relevance hint is given to the provider.
444 \row
445 \li SearchResultModel.DistanceHint
446 \li The distance of the place from the user's current location is important to the user.
447 This hint is only meaningful when a circular search area is used.
448 \row
449 \li SearchResultModel.LexicalPlaceNameHint
450 \li The lexical ordering of place names (in ascending alphabetical order) is relevant to
451 the user. This hint is useful for providers based on a local data store.
452 \endtable
453*/
454QDeclarativeSearchResultModel::RelevanceHint QDeclarativeSearchResultModel::relevanceHint() const
455{
456 return static_cast<QDeclarativeSearchResultModel::RelevanceHint>(m_request.relevanceHint());
457}
458
459void QDeclarativeSearchResultModel::setRelevanceHint(QDeclarativeSearchResultModel::RelevanceHint hint)
460{
461 if (m_request.relevanceHint() != static_cast<QPlaceSearchRequest::RelevanceHint>(hint)) {
462 m_request.setRelevanceHint(static_cast<QPlaceSearchRequest::RelevanceHint>(hint));
463 emit relevanceHintChanged();
464 }
465}
466
467/*!
468 \qmlproperty enum PlaceSearchModel::visibilityScope
469
470 This property holds the visibility scope of the places to search. Only places with the
471 specified visibility will be returned in the search results.
472
473 The visibility scope can be one of:
474
475 \table
476 \row
477 \li Place.UnspecifiedVisibility
478 \li No explicit visibility scope specified, places with any visibility may be part of
479 search results.
480 \row
481 \li Place.DeviceVisibility
482 \li Only places stored on the local device will be part of the search results.
483 \row
484 \li Place.PrivateVisibility
485 \li Only places that are private to the current user will be part of the search results.
486 \row
487 \li Place.PublicVisibility
488 \li Only places that are public will be part of the search results.
489 \endtable
490*/
491QDeclarativePlace::Visibility QDeclarativeSearchResultModel::visibilityScope() const
492{
493 return QDeclarativePlace::Visibility(int(m_visibilityScope));
494}
495
496void QDeclarativeSearchResultModel::setVisibilityScope(QDeclarativePlace::Visibility visibilityScope)
497{
498 QLocation::VisibilityScope scope = QLocation::VisibilityScope(visibilityScope);
499
500 if (m_visibilityScope == scope)
501 return;
502
503 m_visibilityScope = scope;
504 emit visibilityScopeChanged();
505}
506
507/*!
508 \internal
509*/
510QDeclarativeGeoServiceProvider *QDeclarativeSearchResultModel::favoritesPlugin() const
511{
512 return m_favoritesPlugin;
513}
514
515/*!
516 \internal
517*/
518void QDeclarativeSearchResultModel::setFavoritesPlugin(QDeclarativeGeoServiceProvider *plugin)
519{
520
521 if (m_favoritesPlugin == plugin)
522 return;
523
524 m_favoritesPlugin = plugin;
525
526 if (m_favoritesPlugin) {
527 QGeoServiceProvider *serviceProvider = m_favoritesPlugin->sharedGeoServiceProvider();
528 if (serviceProvider) {
529 QPlaceManager *placeManager = serviceProvider->placeManager();
530 if (placeManager) {
531 if (placeManager->childCategoryIds().isEmpty()) {
532 QPlaceReply *reply = placeManager->initializeCategories();
533 connect(reply, &QPlaceReply::finished, reply, &QObject::deleteLater);
534 }
535 }
536 }
537 }
538
539 emit favoritesPluginChanged();
540}
541
542/*!
543 \internal
544*/
545QVariantMap QDeclarativeSearchResultModel::favoritesMatchParameters() const
546{
547 return m_matchParameters;
548}
549
550/*!
551 \internal
552*/
553void QDeclarativeSearchResultModel::setFavoritesMatchParameters(const QVariantMap &parameters)
554{
555 if (m_matchParameters == parameters)
556 return;
557
558 m_matchParameters = parameters;
559 emit favoritesMatchParametersChanged();
560}
561
562/*!
563 \internal
564*/
565int QDeclarativeSearchResultModel::rowCount(const QModelIndex &parent) const
566{
567 Q_UNUSED(parent);
568
569 return m_results.count();
570}
571
572void QDeclarativeSearchResultModel::clearData(bool suppressSignal)
573{
574 QDeclarativeSearchModelBase::clearData(suppressSignal);
575
576 qDeleteAll(m_places);
577 m_places.clear();
578 m_icons.clear();
579 if (!m_results.isEmpty()) {
580 m_results.clear();
581
582 if (!suppressSignal)
583 emit rowCountChanged();
584 }
585}
586
587QVariant QDeclarativeSearchResultModel::data(const QModelIndex &index, int role) const
588{
589 if (index.row() > m_results.count())
590 return QVariant();
591
592 const QPlaceSearchResult &result = m_results.at(index.row());
593
594 switch (role) {
595 case SearchResultTypeRole:
596 return result.type();
597 case Qt::DisplayRole:
598 case TitleRole:
599 return result.title();
600 case IconRole:
601 return QVariant::fromValue(m_icons.at(index.row()));
602 case DistanceRole:
603 if (result.type() == QPlaceSearchResult::PlaceResult) {
604 QPlaceResult placeResult = result;
605 return placeResult.distance();
606 }
607 break;
608 case PlaceRole:
609 if (result.type() == QPlaceSearchResult::PlaceResult)
610 return QVariant::fromValue(static_cast<QObject *>(m_places.at(index.row())));
611 break;
612 case SponsoredRole:
613 if (result.type() == QPlaceSearchResult::PlaceResult) {
614 QPlaceResult placeResult = result;
615 return placeResult.isSponsored();
616 }
617 break;
618 }
619 return QVariant();
620}
621
622/*!
623 \internal
624*/
625QVariant QDeclarativeSearchResultModel::data(int index, const QString &role) const
626{
627 QModelIndex modelIndex = createIndex(index, 0);
628 return data(modelIndex, roleNames().key(role.toLatin1()));
629}
630
631QHash<int, QByteArray> QDeclarativeSearchResultModel::roleNames() const
632{
633 QHash<int, QByteArray> roles = QDeclarativeSearchModelBase::roleNames();
634 roles.insert(SearchResultTypeRole, "type");
635 roles.insert(TitleRole, "title");
636 roles.insert(IconRole, "icon");
637 roles.insert(DistanceRole, "distance");
638 roles.insert(PlaceRole, "place");
639 roles.insert(SponsoredRole, "sponsored");
640
641 return roles;
642}
643
644/*!
645 \qmlmethod void PlaceSearchModel::updateWith(int proposedSearchIndex)
646
647 Updates the model based on the ProposedSearchResult at index \a proposedSearchIndex. The model
648 will be populated with a list of places matching the proposed search. Model status will be set
649 to PlaceSearchModel.Loading. If the model is updated successfully status will be set to
650 PlaceSearchModel.Ready. If an error occurs status will be set to PlaceSearchModel.Error and the
651 model cleared.
652
653 If \a proposedSearchIndex does not reference a ProposedSearchResult this method does nothing.
654*/
655void QDeclarativeSearchResultModel::updateWith(int proposedSearchIndex)
656{
657 if (m_results.at(proposedSearchIndex).type() != QPlaceSearchResult::ProposedSearchResult)
658 return;
659
660 m_request = QPlaceProposedSearchResult(m_results.at(proposedSearchIndex)).searchRequest();
661 update();
662}
663
664QPlaceReply *QDeclarativeSearchResultModel::sendQuery(QPlaceManager *manager,
665 const QPlaceSearchRequest &request)
666{
667 Q_ASSERT(manager);
668 return manager->search(request);
669}
670
671/*!
672 \internal
673*/
674void QDeclarativeSearchResultModel::initializePlugin(QDeclarativeGeoServiceProvider *plugin)
675{
676 //disconnect the manager of the old plugin if we have one
677 if (m_plugin) {
678 QGeoServiceProvider *serviceProvider = m_plugin->sharedGeoServiceProvider();
679 if (serviceProvider) {
680 QPlaceManager *placeManager = serviceProvider->placeManager();
681 if (placeManager) {
682 disconnect(placeManager, &QPlaceManager::placeUpdated,
683 this, &QDeclarativeSearchResultModel::placeUpdated);
684 disconnect(placeManager, &QPlaceManager::placeRemoved,
685 this, &QDeclarativeSearchResultModel::placeRemoved);
686 connect(placeManager, &QPlaceManager::dataChanged,
687 this, &QDeclarativeSearchResultModel::dataChanged);
688 }
689 }
690 }
691
692 //connect to the manager of the new plugin.
693 if (plugin) {
694 QGeoServiceProvider *serviceProvider = plugin->sharedGeoServiceProvider();
695 if (serviceProvider) {
696 QPlaceManager *placeManager = serviceProvider->placeManager();
697 if (placeManager) {
698 connect(placeManager, &QPlaceManager::placeUpdated,
699 this, &QDeclarativeSearchResultModel::placeUpdated);
700 connect(placeManager, &QPlaceManager::placeRemoved,
701 this, &QDeclarativeSearchResultModel::placeRemoved);
702 disconnect(placeManager, &QPlaceManager::dataChanged,
703 this, &QDeclarativeSearchResultModel::dataChanged);
704 }
705 }
706 }
707 QDeclarativeSearchModelBase::initializePlugin(plugin);
708}
709
710/*!
711 \internal
712*/
713void QDeclarativeSearchResultModel::queryFinished()
714{
715 if (!m_reply)
716 return;
717 QPlaceReply *reply = m_reply;
718 m_reply = nullptr;
719 reply->deleteLater();
720
721 if (!m_incremental)
722 m_pages.clear();
723
724 if (reply->error() != QPlaceReply::NoError) {
725 m_resultsBuffer.clear();
726 updateLayout();
727 setStatus(Error, reply->errorString());
728 return;
729 }
730
731 if (reply->type() == QPlaceReply::SearchReply) {
732 QPlaceSearchReply *searchReply = qobject_cast<QPlaceSearchReply *>(reply);
733 Q_ASSERT(searchReply);
734
735 const QPlaceSearchRequestPrivate *rpimpl = QPlaceSearchRequestPrivate::get(searchReply->request());
736 if (!rpimpl->related || !m_incremental)
737 m_pages.clear();
738 m_resultsBuffer = searchReply->results();
739 bool alreadyLoaded = false;
740 if (m_pages.contains(rpimpl->page) && m_resultsBuffer == m_pages.value(rpimpl->page))
741 alreadyLoaded = true;
742 m_pages.insert(rpimpl->page, m_resultsBuffer);
743 setPreviousPageRequest(searchReply->previousPageRequest());
744 setNextPageRequest(searchReply->nextPageRequest());
745
746 // Performing favorite matching only upon finished()
747 if (!m_favoritesPlugin) {
748 updateLayout();
749 setStatus(Ready);
750 } else {
751 QGeoServiceProvider *serviceProvider = m_favoritesPlugin->sharedGeoServiceProvider();
752 if (!serviceProvider) {
753 updateLayout();
754 setStatus(Error, QStringLiteral("Favorites plugin returns a null QGeoServiceProvider instance"));
755 return;
756 }
757
758 QPlaceManager *favoritesManager = serviceProvider->placeManager();
759 if (!favoritesManager) {
760 updateLayout();
761 setStatus(Error, QStringLiteral("Favorites plugin returns a null QPlaceManager"));
762 return;
763 }
764
765 QPlaceMatchRequest request;
766 if (m_matchParameters.isEmpty()) {
767 if (!m_plugin) {
768 setStatus(Error, QStringLiteral("Plugin not assigned"));
769 return;
770 }
771
772 QVariantMap params;
773 params.insert(QPlaceMatchRequest::AlternativeId, QVariant(QString::fromLatin1("x_id_") + m_plugin->name()));
774 request.setParameters(params);
775 } else {
776 request.setParameters(m_matchParameters);
777 }
778
779 request.setResults(m_resultsBuffer);
780 if (alreadyLoaded)
781 m_resultsBuffer.clear();
782 m_reply = favoritesManager->matchingPlaces(request);
783 connect(m_reply, &QPlaceReply::finished,
784 this, &QDeclarativeSearchResultModel::queryFinished);
785 connect(m_reply, &QPlaceReply::contentUpdated,
786 this, &QDeclarativeSearchResultModel::onContentUpdated);
787 }
788 } else if (reply->type() == QPlaceReply::MatchReply) {
789 QPlaceMatchReply *matchReply = qobject_cast<QPlaceMatchReply *>(reply);
790 Q_ASSERT(matchReply);
791 updateLayout(matchReply->places());
792 setStatus(Ready);
793 } else {
794 setStatus(Error, QStringLiteral("Unknown reply type"));
795 }
796}
797
798void QDeclarativeSearchResultModel::onContentUpdated()
799{
800 if (!m_reply)
801 return;
802
803 QPlaceReply *reply = m_reply; // not finished, don't delete.
804
805 if (!m_incremental)
806 m_pages.clear();
807
808 if (reply->error() != QPlaceReply::NoError) {
809 m_resultsBuffer.clear();
810 updateLayout();
811 setStatus(Error, reply->errorString());
812 return;
813 }
814
815 if (reply->type() == QPlaceReply::SearchReply) {
816 QPlaceSearchReply *searchReply = qobject_cast<QPlaceSearchReply *>(reply);
817 Q_ASSERT(searchReply);
818
819 const QPlaceSearchRequestPrivate *rpimpl = QPlaceSearchRequestPrivate::get(searchReply->request());
820 if (!rpimpl->related || !m_incremental)
821 m_pages.clear();
822 m_resultsBuffer = searchReply->results();
823 if (!(m_pages.contains(rpimpl->page) && m_resultsBuffer == m_pages.value(rpimpl->page))) {
824 m_pages.insert(rpimpl->page, m_resultsBuffer);
825 updateLayout();
826 }
827 } else if (reply->type() == QPlaceReply::MatchReply) {
828 // ToDo: handle incremental match replies
829 } else {
830 setStatus(Error, QStringLiteral("Unknown reply type"));
831 }
832}
833
834/*!
835 \qmlmethod Variant PlaceSearchModel::data(int index, string role)
836
837 Returns the data for a given \a role at the specified row \a index.
838*/
839
840/*!
841 \qmlproperty int PlaceSearchModel::count
842
843 This property holds the number of results the model has.
844
845 Note that it does not refer to the total number of search results
846 available in the backend. The total number of search results
847 is not currently supported by the API.
848*/
849
850/*!
851 \internal
852 Note: m_results buffer should be correctly populated before
853 calling this function
854*/
855void QDeclarativeSearchResultModel::updateLayout(const QList<QPlace> &favoritePlaces)
856{
857 const int oldRowCount = rowCount();
858 int start = 0;
859
860 if (m_incremental) {
861 if (!m_resultsBuffer.size())
862 return;
863
864 beginInsertRows(QModelIndex(), oldRowCount , oldRowCount + m_resultsBuffer.size() - 1);
865 m_results = resultsFromPages();
866 start = oldRowCount;
867 } else {
868 beginResetModel();
869 clearData(true);
870 m_results = m_resultsBuffer;
871 }
872
873 m_resultsBuffer.clear();
874 for (qsizetype i = start; i < m_results.count(); ++i) {
875 const QPlaceSearchResult &result = m_results.at(i);
876
877 if (result.type() == QPlaceSearchResult::PlaceResult) {
878 QPlaceResult placeResult = result;
879 QDeclarativePlace *place = new QDeclarativePlace(placeResult.place(), plugin(), this);
880 m_places.append(place);
881
882 if ((favoritePlaces.count() == m_results.count()) && favoritePlaces.at(i) != QPlace())
883 m_places[i]->setFavorite(new QDeclarativePlace(favoritePlaces.at(i),
884 m_favoritesPlugin, m_places[i]));
885 } else if (result.type() == QPlaceSearchResult::ProposedSearchResult) {
886 m_places.append(0);
887 }
888
889 if (!result.icon().isEmpty())
890 m_icons.append(result.icon());
891 }
892
893 if (m_incremental)
894 endInsertRows();
895 else
896 endResetModel();
897 if (m_results.count() != oldRowCount)
898 emit rowCountChanged();
899}
900
901/*!
902 \internal
903*/
904void QDeclarativeSearchResultModel::placeUpdated(const QString &placeId)
905{
906 int row = getRow(placeId);
907 if (row < 0 || row > m_places.count())
908 return;
909
910 if (m_places.at(row))
911 m_places.at(row)->getDetails();
912}
913
914/*!
915 \internal
916*/
917void QDeclarativeSearchResultModel::placeRemoved(const QString &placeId)
918{
919 int row = getRow(placeId);
920 if (row < 0 || row > m_places.count())
921 return;
922
923 beginRemoveRows(QModelIndex(), row, row);
924 delete m_places.at(row);
925 m_places.removeAt(row);
926 m_results.removeAt(row);
927 removePageRow(row);
928 endRemoveRows();
929
930 emit rowCountChanged();
931}
932
933QList<QPlaceSearchResult> QDeclarativeSearchResultModel::resultsFromPages() const
934{
935 QList<QPlaceSearchResult> res;
936 for (const auto &e : m_pages)
937 res.append(e);
938 return res;
939}
940
941void QDeclarativeSearchResultModel::removePageRow(int row)
942{
943 int scanned = 0;
944 for (auto i = m_pages.begin(), end = m_pages.end(); i != end; ++i) {
945 QList<QPlaceSearchResult> &page = i.value();
946 scanned += page.size();
947 if (row >= scanned)
948 continue;
949 page.removeAt(row - scanned + page.size());
950 return;
951 }
952}
953
954/*!
955 \internal
956*/
957int QDeclarativeSearchResultModel::getRow(const QString &placeId) const
958{
959 for (qsizetype i = 0; i < m_places.count(); ++i) {
960 if (!m_places.at(i))
961 continue;
962 else if (m_places.at(i)->placeId() == placeId)
963 return i;
964 }
965
966 return -1;
967}
968
969/*!
970 \qmlsignal PlaceSearchResultModel::dataChanged()
971
972 This signal is emitted when significant changes have been made to the underlying datastore.
973
974 Applications should act on this signal at their own discretion. The data
975 provided by the model could be out of date and so the model should be reupdated
976 sometime, however an immediate reupdate may be disconcerting to users if the results
977 change without any action on their part.
978
979 The corresponding handler is \c onDataChanged.
980*/
981
982QT_END_NAMESPACE