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