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 
52 QT_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 
QDeclarativeSearchResultModel(QObject * parent)355 QDeclarativeSearchResultModel::QDeclarativeSearchResultModel(QObject *parent)
356     :   QDeclarativeSearchModelBase(parent), m_favoritesPlugin(0)
357 {
358 }
359 
~QDeclarativeSearchResultModel()360 QDeclarativeSearchResultModel::~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 */
searchTerm() const369 QString QDeclarativeSearchResultModel::searchTerm() const
370 {
371     return m_request.searchTerm();
372 }
373 
setSearchTerm(const QString & searchTerm)374 void 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 */
categories()391 QQmlListProperty<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 
categories_append(QQmlListProperty<QDeclarativeCategory> * list,QDeclarativeCategory * declCategory)401 void QDeclarativeSearchResultModel::categories_append(QQmlListProperty<QDeclarativeCategory> *list,
402                                                       QDeclarativeCategory *declCategory)
403 {
404     QDeclarativeSearchResultModel *searchModel = qobject_cast<QDeclarativeSearchResultModel *>(list->object);
405     if (searchModel && declCategory) {
406         searchModel->m_request.setSearchContext(QVariant());
407         searchModel->m_categories.append(declCategory);
408         QList<QPlaceCategory> categories = searchModel->m_request.categories();
409         categories.append(declCategory->category());
410         searchModel->m_request.setCategories(categories);
411         emit searchModel->categoriesChanged();
412     }
413 }
414 
categories_count(QQmlListProperty<QDeclarativeCategory> * list)415 int QDeclarativeSearchResultModel::categories_count(QQmlListProperty<QDeclarativeCategory> *list)
416 {
417     QDeclarativeSearchResultModel *searchModel = qobject_cast<QDeclarativeSearchResultModel *>(list->object);
418     if (searchModel)
419         return searchModel->m_categories.count();
420     else
421         return -1;
422 }
423 
category_at(QQmlListProperty<QDeclarativeCategory> * list,int index)424 QDeclarativeCategory *QDeclarativeSearchResultModel::category_at(QQmlListProperty<QDeclarativeCategory> *list,
425                                                                           int index)
426 {
427     QDeclarativeSearchResultModel *searchModel = qobject_cast<QDeclarativeSearchResultModel *>(list->object);
428     if (searchModel && (searchModel->m_categories.count() > index) && (index > -1))
429         return searchModel->m_categories.at(index);
430     else
431         return 0;
432 }
433 
categories_clear(QQmlListProperty<QDeclarativeCategory> * list)434 void QDeclarativeSearchResultModel::categories_clear(QQmlListProperty<QDeclarativeCategory> *list)
435 {
436     QDeclarativeSearchResultModel *searchModel = qobject_cast<QDeclarativeSearchResultModel *>(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 */
recommendationId() const453 QString QDeclarativeSearchResultModel::recommendationId() const
454 {
455     return m_request.recommendationId();
456 }
457 
setRecommendationId(const QString & placeId)458 void 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 */
relevanceHint() const489 QDeclarativeSearchResultModel::RelevanceHint QDeclarativeSearchResultModel::relevanceHint() const
490 {
491     return static_cast<QDeclarativeSearchResultModel::RelevanceHint>(m_request.relevanceHint());
492 }
493 
setRelevanceHint(QDeclarativeSearchResultModel::RelevanceHint hint)494 void 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 */
visibilityScope() const526 QDeclarativePlace::Visibility QDeclarativeSearchResultModel::visibilityScope() const
527 {
528     return QDeclarativePlace::Visibility(int(m_visibilityScope));
529 }
530 
setVisibilityScope(QDeclarativePlace::Visibility visibilityScope)531 void 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 */
favoritesPlugin() const545 QDeclarativeGeoServiceProvider *QDeclarativeSearchResultModel::favoritesPlugin() const
546 {
547     return m_favoritesPlugin;
548 }
549 
550 /*!
551     \internal
552 */
setFavoritesPlugin(QDeclarativeGeoServiceProvider * plugin)553 void 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(reply, SIGNAL(finished()), reply, SLOT(deleteLater()));
569                 }
570             }
571         }
572     }
573 
574     emit favoritesPluginChanged();
575 }
576 
577 /*!
578     \internal
579 */
favoritesMatchParameters() const580 QVariantMap QDeclarativeSearchResultModel::favoritesMatchParameters() const
581 {
582     return m_matchParameters;
583 }
584 
585 /*!
586     \internal
587 */
setFavoritesMatchParameters(const QVariantMap & parameters)588 void 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 */
rowCount(const QModelIndex & parent) const600 int QDeclarativeSearchResultModel::rowCount(const QModelIndex &parent) const
601 {
602     Q_UNUSED(parent);
603 
604     return m_results.count();
605 }
606 
clearData(bool suppressSignal)607 void QDeclarativeSearchResultModel::clearData(bool suppressSignal)
608 {
609     QDeclarativeSearchModelBase::clearData(suppressSignal);
610 
611     qDeleteAll(m_places);
612     m_places.clear();
613     qDeleteAll(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 
data(const QModelIndex & index,int role) const623 QVariant 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(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(static_cast<QObject *>(m_icons.at(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(static_cast<QObject *>(m_places.at(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 */
data(int index,const QString & role) const661 QVariant QDeclarativeSearchResultModel::data(int index, const QString &role) const
662 {
663     QModelIndex modelIndex = createIndex(index, 0);
664     return data(modelIndex, roleNames().key(role.toLatin1()));
665 }
666 
roleNames() const667 QHash<int, QByteArray> QDeclarativeSearchResultModel::roleNames() const
668 {
669     QHash<int, QByteArray> roles = QDeclarativeSearchModelBase::roleNames();
670     roles.insert(SearchResultTypeRole, "type");
671     roles.insert(TitleRole, "title");
672     roles.insert(IconRole, "icon");
673     roles.insert(DistanceRole, "distance");
674     roles.insert(PlaceRole, "place");
675     roles.insert(SponsoredRole, "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 */
updateWith(int proposedSearchIndex)691 void QDeclarativeSearchResultModel::updateWith(int proposedSearchIndex)
692 {
693     if (m_results.at(proposedSearchIndex).type() != QPlaceSearchResult::ProposedSearchResult)
694         return;
695 
696     m_request = QPlaceProposedSearchResult(m_results.at(proposedSearchIndex)).searchRequest();
697     update();
698 }
699 
sendQuery(QPlaceManager * manager,const QPlaceSearchRequest & request)700 QPlaceReply *QDeclarativeSearchResultModel::sendQuery(QPlaceManager *manager,
701                                                       const QPlaceSearchRequest &request)
702 {
703     Q_ASSERT(manager);
704     return manager->search(request);
705 }
706 
707 /*!
708     \internal
709 */
initializePlugin(QDeclarativeGeoServiceProvider * plugin)710 void 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(placeManager, SIGNAL(placeUpdated(QString)), this, SLOT(placeUpdated(QString)));
719                 disconnect(placeManager, SIGNAL(placeRemoved(QString)), this, SLOT(placeRemoved(QString)));
720                 connect(placeManager, SIGNAL(dataChanged()), 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(placeManager, SIGNAL(placeUpdated(QString)), this, SLOT(placeUpdated(QString)));
732                 connect(placeManager, SIGNAL(placeRemoved(QString)), this, SLOT(placeRemoved(QString)));
733                 disconnect(placeManager, SIGNAL(dataChanged()), this, SIGNAL(dataChanged()));
734             }
735         }
736     }
737     QDeclarativeSearchModelBase::initializePlugin(plugin);
738 }
739 
740 /*!
741     \internal
742 */
queryFinished()743 void 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(Error, reply->errorString());
758         return;
759     }
760 
761     if (reply->type() == QPlaceReply::SearchReply) {
762         QPlaceSearchReply *searchReply = qobject_cast<QPlaceSearchReply *>(reply);
763         Q_ASSERT(searchReply);
764 
765         const QPlaceSearchRequestPrivate *rpimpl = QPlaceSearchRequestPrivate::get(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(rpimpl->page) && m_resultsBuffer == m_pages.value(rpimpl->page))
771             alreadyLoaded = true;
772         m_pages.insert(rpimpl->page, 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(Ready);
780         } else {
781             QGeoServiceProvider *serviceProvider = m_favoritesPlugin->sharedGeoServiceProvider();
782             if (!serviceProvider) {
783                 updateLayout();
784                 setStatus(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(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(Error, QStringLiteral("Plugin not assigned"));
799                     return;
800                 }
801 
802                 QVariantMap params;
803                 params.insert(QPlaceMatchRequest::AlternativeId, QVariant(QString::fromLatin1("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(m_reply, SIGNAL(finished()), this, SLOT(queryFinished()));
814             connect(m_reply, SIGNAL(contentUpdated()), this, SLOT(onContentUpdated()));
815         }
816     } else if (reply->type() == QPlaceReply::MatchReply) {
817         QPlaceMatchReply *matchReply = qobject_cast<QPlaceMatchReply *>(reply);
818         Q_ASSERT(matchReply);
819         updateLayout(matchReply->places());
820         setStatus(Ready);
821     } else {
822         setStatus(Error, QStringLiteral("Unknown reply type"));
823     }
824 }
825 
onContentUpdated()826 void 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(Error, reply->errorString());
840         return;
841     }
842 
843     if (reply->type() == QPlaceReply::SearchReply) {
844         QPlaceSearchReply *searchReply = qobject_cast<QPlaceSearchReply *>(reply);
845         Q_ASSERT(searchReply);
846 
847         const QPlaceSearchRequestPrivate *rpimpl = QPlaceSearchRequestPrivate::get(searchReply->request());
848         if (!rpimpl->related || !m_incremental)
849             m_pages.clear();
850         m_resultsBuffer = searchReply->results();
851         if (!(m_pages.contains(rpimpl->page) && m_resultsBuffer == m_pages.value(rpimpl->page))) {
852             m_pages.insert(rpimpl->page, m_resultsBuffer);
853             updateLayout();
854         }
855     } else if (reply->type() == QPlaceReply::MatchReply) {
856         // ToDo: handle incremental match replies
857     } else {
858         setStatus(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 */
updateLayout(const QList<QPlace> & favoritePlaces)883 void 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(QModelIndex(), oldRowCount , oldRowCount + m_resultsBuffer.size() - 1);
893         m_results = resultsFromPages();
894         start = oldRowCount;
895     } else {
896         beginResetModel();
897         clearData(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(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(0);
915         }
916 
917         QDeclarativePlaceIcon *icon = 0;
918         if (!result.icon().isEmpty())
919             icon = new QDeclarativePlaceIcon(result.icon(), plugin(), this);
920         m_icons.append(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 */
placeUpdated(const QString & placeId)934 void 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(row))
941         m_places.at(row)->getDetails();
942 }
943 
944 /*!
945     \internal
946 */
placeRemoved(const QString & placeId)947 void QDeclarativeSearchResultModel::placeRemoved(const QString &placeId)
948 {
949     int row = getRow(placeId);
950     if (row < 0 || row > m_places.count())
951         return;
952 
953     beginRemoveRows(QModelIndex(), row, row);
954     delete m_places.at(row);
955     m_places.removeAt(row);
956     m_results.removeAt(row);
957     removePageRow(row);
958     endRemoveRows();
959 
960     emit rowCountChanged();
961 }
962 
resultsFromPages() const963 QList<QPlaceSearchResult> QDeclarativeSearchResultModel::resultsFromPages() const
964 {
965     QList<QPlaceSearchResult> res;
966     for (const auto &e : m_pages)
967         res.append(e);
968     return res;
969 }
970 
removePageRow(int row)971 void 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(row - scanned + page.size());
980         return;
981     }
982 }
983 
984 /*!
985     \internal
986 */
getRow(const QString & placeId) const987 int 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 
1012 QT_END_NAMESPACE
1013