1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the examples of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:BSD$
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 https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** BSD License Usage
18** Alternatively, you may use this file under the terms of the BSD license
19** as follows:
20**
21** "Redistribution and use in source and binary forms, with or without
22** modification, are permitted provided that the following conditions are
23** met:
24**   * Redistributions of source code must retain the above copyright
25**     notice, this list of conditions and the following disclaimer.
26**   * Redistributions in binary form must reproduce the above copyright
27**     notice, this list of conditions and the following disclaimer in
28**     the documentation and/or other materials provided with the
29**     distribution.
30**   * Neither the name of The Qt Company Ltd nor the names of its
31**     contributors may be used to endorse or promote products derived
32**     from this software without specific prior written permission.
33**
34**
35** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46**
47** $QT_END_LICENSE$
48**
49****************************************************************************/
50
51import QtQuick 2.5
52import QtQuick.Controls 1.4
53import QtQuick.Layouts 1.2
54import QtPositioning 5.5
55import QtLocation 5.6
56import "items"
57
58ApplicationWindow {
59    id: appWindow
60    property Map map
61    property variant parameters
62    property variant searchLocation: map ? map.center : QtPositioning.coordinate()
63    property variant searchRegion: QtPositioning.circle(searchLocation)
64    property variant searchRegionItem
65
66    property Plugin favoritesPlugin
67
68    function getPlugins() {
69        var plugin = Qt.createQmlObject('import QtLocation 5.3; Plugin {}', appWindow);
70        var myArray = new Array;
71        for (var i = 0; i < plugin.availableServiceProviders.length; i++) {
72            var tempPlugin = Qt.createQmlObject ('import QtLocation 5.3; Plugin {name: "' + plugin.availableServiceProviders[i]+ '"}', appWindow)
73
74            if (tempPlugin.supportsPlaces() && tempPlugin.supportsMapping() )
75                myArray.push(tempPlugin.name)
76        }
77        myArray.sort()
78        return myArray;
79    }
80
81    function initializeProviders(pluginParameters)
82    {
83        var parameters = new Array()
84        for (var prop in pluginParameters) {
85            var parameter = Qt.createQmlObject('import QtLocation 5.3; PluginParameter{ name: "'+ prop + '"; value: "' + pluginParameters[prop]+'"}',appWindow)
86            parameters.push(parameter)
87        }
88        appWindow.parameters = parameters
89        var plugins = getPlugins()
90        mainMenu.providerMenu.createMenu(plugins)
91        for (var i = 0; i<plugins.length; i++) {
92            if (plugins[i] === "osm")
93                mainMenu.selectProvider(plugins[i])
94        }
95    }
96
97    function createMap(provider) {
98        var plugin;
99        if (parameters && parameters.length>0)
100            plugin = Qt.createQmlObject ('import QtLocation 5.3; Plugin{ name:"' + provider + '"; parameters: appWindow.parameters}', appWindow)
101        else
102            plugin = Qt.createQmlObject ('import QtLocation 5.3; Plugin{ name:"' + provider + '"}', appWindow)
103
104        if (map)
105            map.destroy();
106        map = mapComponent.createObject(page);
107        map.plugin = plugin;
108        map.zoomLevel = (map.maximumZoomLevel - map.minimumZoomLevel)/2
109        categoryModel.plugin = plugin;
110        categoryModel.update();
111        placeSearchModel.plugin = plugin;
112        suggestionModel.plugin = plugin;
113    }
114
115    title: qsTr("Places")
116    width: 360
117    height: 640
118    visible: true
119    menuBar: mainMenu
120    toolBar: searchBar
121
122    MainMenu {
123        id: mainMenu
124        onSelectProvider: {
125            stackView.pop(page)
126            for (var i = 0; i < providerMenu.items.length; i++) {
127                providerMenu.items[i].checked = providerMenu.items[i].text === providerName
128            }
129
130            createMap(providerName)
131            if (map.error === Map.NoError) {
132                settingsMenu.createMenu(map);
133            } else {
134                settingsMenu.clear();
135            }
136        }
137        onSelectSetting: {
138            stackView.pop({tem:page,immediate: true})
139            switch (setting) {
140            case "searchCenter":
141                stackView.push({ item: Qt.resolvedUrl("forms/SearchCenter.qml") ,
142                                   properties: { "coordinate": map.center}})
143                stackView.currentItem.changeSearchCenter.connect(stackView.changeSearchCenter)
144                stackView.currentItem.closeForm.connect(stackView.closeForm)
145                break
146            case "searchBoundingBox":
147                stackView.push({ item: Qt.resolvedUrl("forms/SearchBoundingBox.qml") ,
148                                   properties: { "searchRegion": searchRegion}})
149                stackView.currentItem.changeSearchBoundingBox.connect(stackView.changeSearchBoundingBox)
150                stackView.currentItem.closeForm.connect(stackView.closeForm)
151                break
152            case "searchBoundingCircle":
153                stackView.push({ item: Qt.resolvedUrl("forms/SearchBoundingCircle.qml") ,
154                                   properties: { "searchRegion": searchRegion}})
155                stackView.currentItem.changeSearchBoundingCircle.connect(stackView.changeSearchBoundingCircle)
156                stackView.currentItem.closeForm.connect(stackView.closeForm)
157                break
158            case "SearchOptions":
159                stackView.push({ item: Qt.resolvedUrl("forms/SearchOptions.qml") ,
160                                   properties: { "plugin": map.plugin,
161                                       "model": placeSearchModel}})
162                stackView.currentItem.changeSearchSettings.connect(stackView.changeSearchSettings)
163                stackView.currentItem.closeForm.connect(stackView.closeForm)
164                break
165            default:
166                console.log("Unsupported setting !")
167            }
168        }
169    }
170
171    //! [PlaceSearchSuggestionModel search text changed 1]
172    SearchBar {
173        id: searchBar
174    //! [PlaceSearchSuggestionModel search text changed 1]
175        width: appWindow.width
176        searchBarVisbile: stackView.depth > 1 &&
177                          stackView.currentItem &&
178                          stackView.currentItem.objectName != "suggestionView" ? false : true
179        onShowCategories: {
180            if (map && map.plugin) {
181                stackView.pop({tem:page,immediate: true})
182                stackView.enterCategory()
183            }
184        }
185        onGoBack: stackView.pop()
186    //! [PlaceSearchSuggestionModel search text changed 2]
187        onSearchTextChanged: {
188            if (searchText.length >= 3 && suggestionModel != null) {
189                suggestionModel.searchTerm = searchText;
190                suggestionModel.update();
191            }
192        }
193    //! [PlaceSearchSuggestionModel search text changed 2]
194        onDoSearch: {
195            if (searchText.length > 0)
196                placeSearchModel.searchForText(searchText);
197        }
198        onShowMap: stackView.pop(page)
199    //! [PlaceSearchSuggestionModel search text changed 3]
200    }
201    //! [PlaceSearchSuggestionModel search text changed 3]
202
203    StackView {
204        id: stackView
205
206        function showMessage(title,message,backPage)
207        {
208            push({ item: Qt.resolvedUrl("forms/Message.qml") ,
209                     properties: {
210                         "title" : title,
211                         "message" : message,
212                         "backPage" : backPage
213                     }})
214            currentItem.closeForm.connect(closeMessage)
215        }
216
217        function closeMessage(backPage)
218        {
219            pop(backPage)
220        }
221
222        function closeForm()
223        {
224            pop(page)
225        }
226
227        function enterCategory(index)
228        {
229            push({ item: Qt.resolvedUrl("views/CategoryView.qml") ,
230                     properties: { "categoryModel": categoryModel,
231                         "rootIndex" : index
232                     }})
233            currentItem.showSubcategories.connect(stackView.enterCategory)
234            currentItem.searchCategory.connect(placeSearchModel.searchForCategory)
235        }
236
237        function showSuggestions()
238        {
239            if (currentItem.objectName != "suggestionView") {
240                stackView.pop(page)
241                push({ item: Qt.resolvedUrl("views/SuggestionView.qml") ,
242                         properties: { "suggestionModel": suggestionModel }
243                     })
244                currentItem.objectName = "suggestionView"
245                currentItem.suggestionSelected.connect(searchBar.showSearch)
246                currentItem.suggestionSelected.connect(placeSearchModel.searchForText)
247            }
248        }
249
250        function showPlaces()
251        {
252            if (currentItem.objectName != "searchResultView") {
253                stackView.pop({tem:page,immediate: true})
254                push({ item: Qt.resolvedUrl("views/SearchResultView.qml") ,
255                         properties: { "placeSearchModel": placeSearchModel }
256                     })
257                currentItem.showPlaceDetails.connect(showPlaceDatails)
258                currentItem.showMap.connect(searchBar.showMap)
259                currentItem.objectName = "searchResultView"
260            }
261        }
262
263        function showPlaceDatails(place, distance)
264        {
265            push({ item: Qt.resolvedUrl("forms/PlaceDetails.qml") ,
266                     properties: { "place": place,
267                         "distanceToPlace": distance }
268                 })
269            currentItem.searchForSimilar.connect(searchForSimilar)
270            currentItem.showReviews.connect(showReviews)
271            currentItem.showEditorials.connect(showEditorials)
272            currentItem.showImages.connect(showImages)
273        }
274
275        function showEditorials(place)
276        {
277            push({ item: Qt.resolvedUrl("views/EditorialView.qml") ,
278                     properties: { "place": place }
279                 })
280            currentItem.showEditorial.connect(showEditorial)
281        }
282
283        function showReviews(place)
284        {
285            push({ item: Qt.resolvedUrl("views/ReviewView.qml") ,
286                     properties: { "place": place }
287                 })
288            currentItem.showReview.connect(showReview)
289        }
290
291        function showImages(place)
292        {
293            push({ item: Qt.resolvedUrl("views/ImageView.qml") ,
294                     properties: { "place": place }
295                 })
296        }
297
298        function showEditorial(editorial)
299        {
300            push({ item: Qt.resolvedUrl("views/EditorialPage.qml") ,
301                     properties: { "editorial": editorial }
302                 })
303        }
304
305        function showReview(review)
306        {
307            push({ item: Qt.resolvedUrl("views/ReviewPage.qml") ,
308                     properties: { "review": review }
309                 })
310        }
311
312        function changeSearchCenter(coordinate)
313        {
314            stackView.pop(page)
315            map.center = coordinate;
316            if (searchRegionItem) {
317                map.removeMapItem(searchRegionItem);
318                searchRegionItem.destroy();
319            }
320        }
321
322        function changeSearchBoundingBox(coordinate,widthDeg,heightDeg)
323        {
324            stackView.pop(page)
325            map.center = coordinate
326            searchRegion = QtPositioning.rectangle(map.center, widthDeg, heightDeg)
327            if (searchRegionItem) {
328                map.removeMapItem(searchRegionItem);
329                searchRegionItem.destroy();
330            }
331            searchRegionItem = Qt.createQmlObject('import QtLocation 5.3; MapRectangle { color: "#46a2da"; border.color: "#190a33"; border.width: 2; opacity: 0.25 }', page, "MapRectangle");
332            searchRegionItem.topLeft = searchRegion.topLeft;
333            searchRegionItem.bottomRight = searchRegion.bottomRight;
334            map.addMapItem(searchRegionItem);
335        }
336
337        function changeSearchBoundingCircle(coordinate,radius)
338        {
339            stackView.pop(page)
340            map.center = coordinate;
341            searchRegion = QtPositioning.circle(coordinate, radius)
342
343            if (searchRegionItem) {
344                map.removeMapItem(searchRegionItem);
345                searchRegionItem.destroy();
346            }
347            searchRegionItem = Qt.createQmlObject('import QtLocation 5.3; MapCircle { color: "#46a2da"; border.color: "#190a33"; border.width: 2; opacity: 0.25 }', page, "MapRectangle");
348            searchRegionItem.center = searchRegion.center;
349            searchRegionItem.radius = searchRegion.radius;
350            map.addMapItem(searchRegionItem);
351        }
352
353        function changeSearchSettings(orderByDistance, orderByName, locales)
354        {
355            stackView.pop(page)
356            /*if (isFavoritesEnabled) {
357                if (favoritesPlugin == null)
358                    favoritesPlugin = Qt.createQmlObject('import QtLocation 5.3; Plugin { name: "places_jsondb" }', page);
359                favoritesPlugin.parameters = pluginParametersFromMap(pluginParameters);
360                placeSearchModel.favoritesPlugin = favoritesPlugin;
361            } else {
362                placeSearchModel.favoritesPlugin = null;
363            }*/
364            placeSearchModel.favoritesPlugin = null;
365
366            placeSearchModel.relevanceHint = orderByDistance ? PlaceSearchModel.DistanceHint :
367                                                               orderByName ? PlaceSearchModel.LexicalPlaceNameHint :
368                                                                             PlaceSearchModel.UnspecifiedHint;
369            map.plugin.locales = locales.split(Qt.locale().groupSeparator);
370        }
371
372        //! [PlaceRecommendationModel search]
373        function searchForSimilar(place) {
374            stackView.pop(page)
375            searchBar.showSearch(place.name)
376            placeSearchModel.searchForRecommendations(place.placeId);
377        }
378        //! [PlaceRecommendationModel search]
379
380        anchors.fill: parent
381        focus: true
382        initialItem:  Item {
383            id: page
384
385            //! [PlaceSearchModel model]
386            PlaceSearchModel {
387                id: placeSearchModel
388                searchArea: searchRegion
389
390                function searchForCategory(category) {
391                    searchTerm = "";
392                    categories = category;
393                    recommendationId = "";
394                    searchArea = searchRegion
395                    limit = -1;
396                    update();
397                }
398
399                function searchForText(text) {
400                    searchTerm = text;
401                    categories = null;
402                    recommendationId = "";
403                    searchArea = searchRegion
404                    limit = -1;
405                    update();
406                }
407
408                function searchForRecommendations(placeId) {
409                    searchTerm = "";
410                    categories = null;
411                    recommendationId = placeId;
412                    searchArea = null;
413                    limit = -1;
414                    update();
415                }
416
417                onStatusChanged: {
418                    switch (status) {
419                    case PlaceSearchModel.Ready:
420                        if (count > 0)
421                            stackView.showPlaces()
422                        else
423                            stackView.showMessage(qsTr("Search Place Error"),qsTr("Place not found !"))
424                        break;
425                    case PlaceSearchModel.Error:
426                        stackView.showMessage(qsTr("Search Place Error"),errorString())
427                        break;
428                    }
429                }
430            }
431            //! [PlaceSearchModel model]
432
433            //! [PlaceSearchSuggestionModel model]
434            PlaceSearchSuggestionModel {
435                id: suggestionModel
436                searchArea: searchRegion
437
438                onStatusChanged: {
439                    if (status == PlaceSearchSuggestionModel.Ready)
440                        stackView.showSuggestions()
441                }
442            }
443            //! [PlaceSearchSuggestionModel model]
444
445            //! [CategoryModel model]
446            CategoryModel {
447                id: categoryModel
448                hierarchical: true
449            }
450            //! [CategoryModel model]
451
452            Component {
453                id: mapComponent
454
455                MapComponent {
456                    width: page.width
457                    height: page.height
458
459                    onErrorChanged: {
460                        if (map.error != Map.NoError) {
461                            var title = qsTr("ProviderError");
462                            var message =  map.errorString + "<br/><br/><b>" + qsTr("Try to select other provider") + "</b>";
463                            if (map.error == Map.MissingRequiredParameterError)
464                                message += "<br/>" + qsTr("or see") + " \'mapviewer --help\' "
465                                        + qsTr("how to pass plugin parameters.");
466                            stackView.showMessage(title,message);
467                        }
468                    }
469
470                    MapItemView {
471                        model: placeSearchModel
472                        delegate: MapQuickItem {
473                            coordinate: model.type === PlaceSearchModel.PlaceResult ? place.location.coordinate : QtPositioning.coordinate()
474
475                            visible: model.type === PlaceSearchModel.PlaceResult
476
477                            anchorPoint.x: image.width * 0.28
478                            anchorPoint.y: image.height
479
480                            sourceItem: Image {
481                                id: image
482                                source: "resources/marker.png"
483                                MouseArea {
484                                    anchors.fill: parent
485                                    onClicked: stackView.showPlaceDatails(model.place,model.distance)
486                                }
487                            }
488                        }
489                    }
490                }
491            }
492        }
493    }
494
495    Rectangle {
496        color: "white"
497        opacity: busyIndicator.running ? 0.8 : 0
498        anchors.fill: parent
499        Behavior on opacity { NumberAnimation{} }
500    }
501    BusyIndicator {
502        id: busyIndicator
503        anchors.centerIn: parent
504        running: placeSearchModel.status == PlaceSearchModel.Loading ||
505                 categoryModel.status === CategoryModel.Loading
506    }
507}
508