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

source code of qtlocation/src/location/declarativeplaces/qdeclarativesearchresultmodel.cpp